package com.atlassian.bitbucket.internal.branch.model.dao;

import com.atlassian.activeobjects.test.TestActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDaoTest;
import com.atlassian.bitbucket.hamcrest.MoreMatchers;
import com.atlassian.bitbucket.internal.branch.model.configuration.BranchConfiguration;
import com.atlassian.bitbucket.internal.branch.model.configuration.BranchModelConfigurationRequest;
import com.atlassian.bitbucket.internal.branch.model.configuration.BranchTypeConfiguration;
import com.atlassian.bitbucket.repository.Repository;
import net.java.ao.test.jdbc.Data;
import org.junit.Before;
import org.junit.Test;

import java.util.Set;

import static com.atlassian.bitbucket.internal.branch.model.BranchTypes.*;
import static com.atlassian.bitbucket.internal.branch.model.dao.BranchModelTestData.EMPTY_MODEL_REPOSITORY_ID;
import static com.atlassian.bitbucket.internal.branch.model.dao.BranchModelTestData.FULL_BRANCH_MODEL_REPOSITORY_ID;
import static com.atlassian.bitbucket.internal.branch.model.matchers.BranchConfigurationMatchers.isConcreteRefId;
import static com.atlassian.bitbucket.internal.branch.model.matchers.BranchConfigurationMatchers.isDefaultBranch;
import static com.atlassian.bitbucket.internal.branch.model.matchers.BranchTypeConfigurationMatchers.disabledTypeConfiguration;
import static com.atlassian.bitbucket.internal.branch.model.matchers.BranchTypeConfigurationMatchers.enabledTypeConfiguration;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@SuppressWarnings("ConstantConditions")
@Data(BranchModelTestData.class)
public class AoBranchModelDaoTest extends AbstractAoDaoTest {

    private static final int NON_EXISTING_BRANCH_MODEL_REPOSITORY_ID = 10;

    private AoBranchModelDao dao;

    public AoBranchModelDaoTest() {
        super(AoBranchModelConfiguration.class, AoBranchTypeConfiguration.class);
    }

    @Before
    public void initDao() {
        dao = new AoBranchModelDao(new TestActiveObjects(entityManager));
    }

    @Test
    public void testGetExistingBranchModel() {
        PersistentBranchModelConfiguration configuration = dao.getByRepository(
                newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID));
        assertNotNull("branch model configuration", configuration);
        assertTrue(configuration.isEnabled());
        assertEquals("refs/heads/develop", configuration.getDevelopment().getRefId());
        assertNotNull("production", configuration.getProduction());
        assertEquals("refs/heads/master", configuration.getProduction().getRefId());
        assertNotNull("types", configuration.getTypes());
        assertEquals(4, configuration.getTypes().size());
    }


    @Test
    public void testGetEmptyBranchModel() {
        PersistentBranchModelConfiguration configuration = dao.getByRepository(
                newRepositoryWithId(EMPTY_MODEL_REPOSITORY_ID));
        assertNotNull("branch model configuration", configuration);
        assertTrue(configuration.isEnabled());
        assertNull(configuration.getDevelopment().getRefId());
        assertTrue(configuration.getDevelopment().isUseDefault());
        assertNull(configuration.getProduction());
        assertNotNull("types", configuration.getTypes());
        assertEquals(4, configuration.getTypes().size());
        for (BranchTypeConfiguration typeConfiguration : configuration.getTypes()) {
            assertFalse(typeConfiguration.isEnabled());
        }
    }

    @Test
    public void testGetNonExistingBranchModel() {
        assertNull(dao.getByRepository(newRepositoryWithId(NON_EXISTING_BRANCH_MODEL_REPOSITORY_ID)));
    }

    @Test(expected = NullPointerException.class)
    public void testGetByNullRepository() {
        dao.getByRepository(null);
    }

    @Test
    public void testDisableBranchModel() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        assertTrue(dao.setEnabled(repository, false));
        PersistentBranchModelConfiguration configuration = dao.getByRepository(repository);
        assertNotNull(configuration);
        assertFalse(configuration.isEnabled());
    }

    @Test
    public void testEnableAlreadyEnabledBranchModel() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        assertFalse(dao.setEnabled(repository, true));
    }

    @Test
    public void testCreateEnabledBranchModel() {
        Repository repository = newRepositoryWithId(NON_EXISTING_BRANCH_MODEL_REPOSITORY_ID);
        PersistentBranchModelConfiguration configuration = dao.create(repository,
                new BranchModelConfigurationRequest.Builder()
                        .developmentRefId("refs/heads/master")
                        .useDefaultAsProduction(true)
                        .type(BUGFIX.getId(), "a-bugfix/", true)
                        .type(FEATURE.getId(), "a-feature/", true)
                        .type(HOTFIX.getId(), "a-hotfix/", false)
                        .build()
        );
        assertNotNull(configuration);
        assertTrue(configuration.isEnabled());
        assertNotNull("Development Branch Configuration", configuration.getDevelopment());
        assertEquals("refs/heads/master", configuration.getDevelopment().getRefId());
        assertNotNull(configuration.getProduction());
        assertTrue(configuration.getProduction().isUseDefault());
        // only types in the request are created
        assertEquals(3, configuration.getTypes().size());
    }

    @Test
    public void testDeleteExistingBranchModel() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        dao.deleteByRepository(repository);
        assertNull(dao.getByRepository(repository));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testDeleteRemovesAssociatedTypes() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        dao.deleteByRepository(repository);
        // re-create branch model to make sure old data got wiped out
        dao.create(repository, new BranchModelConfigurationRequest.Builder()
                .developmentRefId("refs/heads/a-master")
                .useDefaultAsProduction(true)
                .type(BUGFIX.getId(), "a-bugfix/", true)
                .type(FEATURE.getId(), "a-feature/", true)
                .type(HOTFIX.getId(), "a-hotfix/", false)
                .build()
        );
        PersistentBranchModelConfiguration configuration = dao.getByRepository(repository);
        assertThat(configuration.getDevelopment(), isConcreteRefId("refs/heads/a-master"));
        assertThat(configuration.getProduction(), isDefaultBranch());
        assertEquals(3, configuration.getTypes().size());
        assertThat(configuration.getTypes(), MoreMatchers.contains(Set.class, BranchTypeConfiguration.class,
                enabledTypeConfiguration(BUGFIX.getId(), "a-bugfix/"),
                enabledTypeConfiguration(FEATURE.getId(), "a-feature/"),
                disabledTypeConfiguration(HOTFIX.getId(), "a-hotfix/")
        ));
    }

    @Test
    public void testDeleteNonExistingBranchModelIsNoop() {
        dao.deleteByRepository(newRepositoryWithId(NON_EXISTING_BRANCH_MODEL_REPOSITORY_ID));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testUpdateFullBranchModel() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        dao.update(repository, new BranchModelConfigurationRequest.Builder()
                .developmentRefId("refs/heads/a-master")
                .useDefaultAsProduction(true)
                .type(BUGFIX.getId(), "a-bugfix/", true)
                .type(FEATURE.getId(), "a-feature/", true)
                .type(HOTFIX.getId(), "a-hotfix/", false)
                .build());
        PersistentBranchModelConfiguration configuration = dao.getByRepository(repository);
        assertThat(configuration.getDevelopment(), isConcreteRefId("refs/heads/a-master"));
        assertThat(configuration.getProduction(), isDefaultBranch());
        // the original model has 4 types and it should stay like that. BUGFIX and FEATURE should remain enabled,
        // HOTFIX should be disabled with the new prefix, and RELEASE should be disabled and have the prefix cleared out
        assertEquals(4, configuration.getTypes().size());
        assertThat(configuration.getTypes(), MoreMatchers.contains(Set.class, BranchTypeConfiguration.class,
                enabledTypeConfiguration(BUGFIX.getId(), "a-bugfix/"),
                enabledTypeConfiguration(FEATURE.getId(), "a-feature/"),
                disabledTypeConfiguration(HOTFIX.getId(), "a-hotfix/"),
                disabledTypeConfiguration(RELEASE.getId())
        ));
    }

    @Test
    public void testUpdateBranchModelNoProduction() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        dao.update(repository, new BranchModelConfigurationRequest.Builder()
                .developmentRefId("refs/heads/a-master")
                .type(BUGFIX.getId(), "a-bugfix/", true)
                .build());
        PersistentBranchModelConfiguration configuration = dao.getByRepository(repository);
        assertThat(configuration.getDevelopment(), isConcreteRefId("refs/heads/a-master"));
        assertThat(configuration.getProduction(), nullValue(BranchConfiguration.class));
        // the original model has 4 types and it should stay like that. we don't care about the details in this test
        assertEquals(4, configuration.getTypes().size());
    }

    @Test
    public void testUpdateBranchModelDevelopmentRefIdUseDefault() {
        Repository repository = newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID);
        dao.update(repository, new BranchModelConfigurationRequest.Builder()
                .useDefaultAsDevelopment(true)
                .type(BUGFIX.getId(), "a-bugfix/", true)
                .build());
        PersistentBranchModelConfiguration configuration = dao.getByRepository(repository);
        assertThat(configuration.getDevelopment(), isDefaultBranch());
        assertThat(configuration.getProduction(), nullValue(BranchConfiguration.class));
        // the original model has 4 types and it should stay like that. we don't care about the details in this test
        assertEquals(4, configuration.getTypes().size());
    }

    @Test(expected = NullPointerException.class)
    public void testUpdateNullRepository() {
        dao.update(null, new BranchModelConfigurationRequest.Builder().build());
    }

    @Test(expected = NullPointerException.class)
    public void testUpdateNullRequest() {
        dao.update(newRepositoryWithId(FULL_BRANCH_MODEL_REPOSITORY_ID), null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUpdateNonExistingBranchModel() {
        dao.update(newRepositoryWithId(NON_EXISTING_BRANCH_MODEL_REPOSITORY_ID),
                new BranchModelConfigurationRequest.Builder().build());
    }

    private static Repository newRepositoryWithId(int repositoryId) {
        Repository repository = mock(Repository.class);
        when(repository.getId()).thenReturn(repositoryId);

        return repository;
    }
}
