package com.atlassian.stash.internal.backup.liquibase;

import com.atlassian.fugue.Effect;
import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.event.CancelableEvent;
import com.atlassian.stash.internal.comment.CommentDao;
import com.atlassian.stash.internal.comment.HibernateCommentDao;
import com.atlassian.stash.internal.comment.InternalComment;
import com.atlassian.stash.internal.jdbc.DataSourceConfiguration;
import com.atlassian.stash.internal.project.HibernateProjectDao;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.project.ProjectDao;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.pull.PullRequestDao;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageRequestImpl;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assume.assumeThat;
import static org.mockito.Mockito.mock;

/**
 * Integration test that checks that null characters (U+0000) in text columns
 * are automatically removed when migrating to a Postgres database.
 */
public class PostgresNullCharactersMigrationTest extends AbstractLiquibaseTest {

    private static final String BACKUP_WITH_NULLS = "/backups/backup-with-null-characters.xml";

    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Autowired
    private DataSourceConfiguration dataSourceConfiguration;
    @Dao(HibernateCommentDao.class)
    private CommentDao commentDao;
    @Dao(HibernateProjectDao.class)
    private ProjectDao projectDao;
    @Dao(HibernateCommentDao.class)
    private PullRequestDao pullRequestDao;

    private DefaultLiquibaseMigrationDao migrationDao;

    @Before
    public void setUp() {
        assumeThat(dataSourceConfiguration.getUrl().toLowerCase(), containsString("postgres"));

        migrationDao = new DefaultLiquibaseMigrationDao(liquibaseDao, new DefaultLiquibaseXmlWriterFactory());
    }

    @Test
    public void testMigration() throws IOException {
        // restore a backup containing U+0000 characters to the target database
        try (InputStream in = new BufferedInputStream(this.getClass().getResourceAsStream(BACKUP_WITH_NULLS))) {
            migrationDao.restore(in, tempFolder.getRoot(), mock(LiquibaseRestoreMonitor.class), mock(CancelableEvent.class));
        }

        // check the restored data has no U+0000 characters
        forAll(commentDao, new Effect<InternalComment>() {
            @Override
            public void apply(InternalComment comment) {
                assertThat(comment.getText(), not(containsString("\u0000")));
            }
        });
        forAll(projectDao, new Effect<InternalProject>() {
            @Override
            public void apply(InternalProject project) {
                assertThat(project.getDescription(), not(containsString("\u0000")));
            }
        });
        forAll(pullRequestDao, new Effect<InternalPullRequest>() {
            @Override
            public void apply(InternalPullRequest pullRequest) {
                assertThat(pullRequest.getDescription(), not(containsString("\u0000")));
                assertThat(pullRequest.getTitle(), not(containsString("\u0000")));
            }
        });
    }

//    /* code used to generate the content of the BACKUP_WITH_NULLS changelog */
//    protected void populateData() {
//        InternalNormalUser user = stashUserDao.create(new InternalNormalUser.Builder().name("ann.nonym").build());
//        InternalProject project = projectDao.create(new InternalNormalProject.Builder()
//                .description("Sample project with \u0000 in description").key("project").name("TestProject").build());
//        InternalRepository repository = repositoryDao.create(new InternalRepository.Builder()
//                .name("repository").hierarchyId("hierarchy-id").project(project).scmId("git").state(Repository.State.AVAILABLE).build());
//
//        pullRequestDao.create(new InternalPullRequest.Builder()
//                .createdDate(new Date())
//                .description("description\u0000 with zero")
//                .title("title")
//                .fromRef(new InternalPullRequestRef.Builder().displayId("source1").id("refs/heads/source1").hash("abc123").repository(repository).build())
//                .toRef(new InternalPullRequestRef.Builder().displayId("target1").id("refs/heads/target1").hash("def456").repository(repository).build())
//                .state(PullRequestState.OPEN)
//                .build());
//
//        commentDao.create(new InternalComment.Builder()
//                .createdDate(new Date()).updatedDate(new Date()).text("comment\u0000with zero and CJK \u6e2c\u8a66")
//                .author(user).build());
//        commentDao.create(new InternalComment.Builder()
//                .createdDate(new Date()).updatedDate(new Date()).text("comment without zero but arabic \u0627\u062e\u062a\u0628\u0627\u0631")
//                .author(user).build());
//    }

    private <T> void forAll(com.atlassian.stash.internal.Dao<?, T> dao, Effect<T> effect) {
        PageRequest pageRequest = new PageRequestImpl(0, 99);
        Page<T> entities;
        do {
            entities = dao.findAll(pageRequest);
            for (T entity : entities.getValues()) {
                effect.apply(entity);
            }
            pageRequest = entities.getNextPageRequest();
        } while (!entities.getIsLastPage());
    }

}
