package com.atlassian.bitbucket.internal.mirroring.mirror.dao;

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.activeobjects.test.TestActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDaoTest;
import com.atlassian.bitbucket.util.PageUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import net.java.ao.test.jdbc.Data;
import org.junit.Before;
import org.junit.Test;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.atlassian.bitbucket.hamcrest.TimeMatchers.closeTo;
import static com.atlassian.bitbucket.internal.mirroring.dao.AoConstants.MAX_IN_CLAUSE_SIZE;
import static org.junit.Assert.*;

@Data(AoDatabaseUpdater.class)
public class AoRepositoryMappingDaoTest extends AbstractAoDaoTest {

    private static final String UPSTREAM_SERVER_ID = "upstream-server-id";
    private AoRepositoryMappingDao dao;
    private AoProjectMappingDao projectMappingDao;

    public AoRepositoryMappingDaoTest() {
        super(AoRepositoryMapping.class);
    }

    @Before
    public void setUp() throws Exception {
        ActiveObjects ao = new TestActiveObjects(entityManager);
        dao = new AoRepositoryMappingDao(ao);
        projectMappingDao = new AoProjectMappingDao(ao);
    }

    @Test
    public void testCountAllInProgress() {
        projectMappingDao.create(42, "upstream-server", "proj1");
        projectMappingDao.create(43, "upstream-server", "proj2");
        dao.create(99, "upstream-server", 42, "aa-bb-cc-dd");
        dao.create(2, "upstream-server", 42, "aa-bb-cc-dd-2");
        dao.create(3, "upstream-server", 43, "aa-bb-cc-dd-3");

        assertEquals(3, dao.countAllInProgress("upstream-server"));

        dao.updateSyncDate(99, new Date());
        dao.updateSyncDate(2, new Date());
        assertEquals(3, dao.countAllInProgress("upstream-server"));

        dao.updateSyncDate(3, new Date());
        assertEquals(3, dao.countAllInProgress("upstream-server"));

        projectMappingDao.clearAllProgress("upstream-server");
        assertEquals(0, dao.countAllInProgress("upstream-server"));
    }

    @Test
    public void testCountSyncedInProgress() {
        projectMappingDao.create(42, "upstream-server", "proj1");
        projectMappingDao.create(43, "upstream-server", "proj2");
        dao.create(99, "upstream-server", 42, "aa-bb-cc-dd");
        dao.create(2, "upstream-server", 42, "aa-bb-cc-dd-2");
        dao.create(3, "upstream-server", 43, "aa-bb-cc-dd-3");

        assertEquals(0, dao.countSyncedInProgress("upstream-server"));

        dao.updateSyncDate(99, new Date());
        dao.updateSyncDate(2, new Date());
        assertEquals(2, dao.countSyncedInProgress("upstream-server"));

        dao.updateSyncDate(3, new Date());
        assertEquals(3, dao.countSyncedInProgress("upstream-server"));

        projectMappingDao.clearAllProgress("upstream-server");
        assertEquals(0, dao.countSyncedInProgress("upstream-server"));
    }

    @Test
    public void testCreate() {
        AoRepositoryMapping mapping = dao.create(99, "upstream-server", 42, "aa-bb-cc-dd");

        assertMapping(99, "upstream-server", 42, "aa-bb-cc-dd", mapping);
    }

    @Test
    public void testDelete() {
        AoRepositoryMapping mapping = dao.create(99, "upstream-server", 42, "aa-bb-cc-dd");
        dao.delete(mapping.getUpstreamId(), mapping.getExternalId());

        assertNull(dao.getByUpstreamId("upstream-server", "aa-bb-cc-dd"));
    }

    @Test
    public void testFindByUpstreamId() throws Exception {
        dao.create(1, "upstream-server", 42, "aa-bb-cc-dd-1");
        dao.create(2, "upstream-server", 42, "aa-bb-cc-dd-2");
        dao.create(3, "upstream-server", 43, "aa-bb-cc-dd-3");

        Iterable<AoRepositoryMapping> values =
                dao.findByUpstreamId("upstream-server", PageUtils.newRequest(0, 3)).getValues();

        assertEquals("aa-bb-cc-dd-1", Iterables.get(values, 0).getExternalId());
        assertEquals("aa-bb-cc-dd-2", Iterables.get(values, 1).getExternalId());
        assertEquals("aa-bb-cc-dd-3", Iterables.get(values, 2).getExternalId());
    }

    @Test
    public void testFindByNoSuchUpstreamId() throws Exception {
        assertEquals(0, dao.findByUpstreamId("upstream-server", PageUtils.newRequest(0, 3)).getSize());
    }

    @Test
    public void testGetByLocalId() {
        dao.create(101, UPSTREAM_SERVER_ID, 42, "bb-cc-dd");

        AoRepositoryMapping retrieved = dao.getByLocalId(101);
        assertMapping(101, UPSTREAM_SERVER_ID, 42, "bb-cc-dd", retrieved);
    }

    @Test
    public void testGetByLocalIdDoesNotExist() {
        assertNull(dao.getByLocalId(111111));
    }

    @Test
    public void testGetByUpstreamId() {
        dao.create(101, UPSTREAM_SERVER_ID, 42, "bb-cc-dd");

        AoRepositoryMapping retrieved = dao.getByUpstreamId(UPSTREAM_SERVER_ID, "bb-cc-dd");
        assertMapping(101, UPSTREAM_SERVER_ID, 42, "bb-cc-dd", retrieved);
    }

    @Test
    public void testGetByUpstreamIdDoesNotExist() {
        dao.create(101, UPSTREAM_SERVER_ID, 42, "bb-cc-dd");

        assertNull(dao.getByUpstreamId("does-not-exist", "bb-cc-dd"));
        assertNull(dao.getByUpstreamId(UPSTREAM_SERVER_ID, "does-not-exist"));
        assertNull(dao.getByUpstreamId("does-not-exist", "nope"));
    }

    @Test
    public void testGetByUpstreamIdBatch() {
        dao.create(101, UPSTREAM_SERVER_ID, 42, "aa-bb-cc");
        dao.create(102, UPSTREAM_SERVER_ID, 42, "bb-cc-dd");
        dao.create(103, UPSTREAM_SERVER_ID, 43, "cc-dd-ee");

        List<AoRepositoryMapping> mapping =
                Lists.newArrayList(dao.getByUpstreamId(UPSTREAM_SERVER_ID, Arrays.asList("aa-bb-cc", "cc-dd-ee")));
        assertEquals(2, mapping.size());
        assertMapping(101, UPSTREAM_SERVER_ID, 42, "aa-bb-cc", mapping.get(0));
        assertMapping(103, UPSTREAM_SERVER_ID, 43, "cc-dd-ee", mapping.get(1));
    }

    @Test
    public void testGetByUpstreamIdBatchCorrectServer() {
        dao.create(101, UPSTREAM_SERVER_ID, 42, "aa-bb-cc");
        dao.create(102, "different-server", 42, "bb-cc-dd");
        dao.create(103, UPSTREAM_SERVER_ID, 43, "cc-dd-ee");

        List<AoRepositoryMapping> mapping =
                Lists.newArrayList(dao.getByUpstreamId(UPSTREAM_SERVER_ID, Arrays.asList("aa-bb-cc", "bb-cc-dd")));
        assertEquals(1, mapping.size());
        assertMapping(101, UPSTREAM_SERVER_ID, 42, "aa-bb-cc", mapping.get(0));
    }

    @Test
    public void testGetByUpstreamIdLargeBatch() {
        ArrayList<String> externalIds = new ArrayList<>();
        for (int i = 0; i <= MAX_IN_CLAUSE_SIZE; i++) {
            String externalId = String.valueOf((i + 1));
            dao.create(i, UPSTREAM_SERVER_ID, 42, externalId);
            externalIds.add(externalId);
        }

        List<AoRepositoryMapping> mapping =
                Lists.newArrayList(dao.getByUpstreamId(UPSTREAM_SERVER_ID, externalIds));
        assertEquals(101, mapping.size());
        assertMapping(0, UPSTREAM_SERVER_ID, 42, "1", mapping.get(0));
        assertMapping(1, UPSTREAM_SERVER_ID, 42, "2", mapping.get(1));
        assertMapping(99, UPSTREAM_SERVER_ID, 42, "100", mapping.get(99));
        assertMapping(MAX_IN_CLAUSE_SIZE, UPSTREAM_SERVER_ID, 42,
                String.valueOf(MAX_IN_CLAUSE_SIZE + 1), mapping.get(MAX_IN_CLAUSE_SIZE));
    }

    @Test
    public void testGetLocalProjectIdsByUpstreamId() throws Exception {
        dao.create(101, UPSTREAM_SERVER_ID, 41, "aa-bb-cc");
        dao.create(102, "different-server", 42, "bb-cc-dd");
        dao.create(103, UPSTREAM_SERVER_ID, 41, "cc-dd-ee");
        dao.create(104, "different-server", 43, "dd-ee-ff");

        assertEquals(Collections.singleton(41), dao.getLocalProjectIdsByUpstreamId(UPSTREAM_SERVER_ID));
        assertEquals(ImmutableSet.of(42, 43), dao.getLocalProjectIdsByUpstreamId("different-server"));
        assertTrue(dao.getLocalProjectIdsByUpstreamId("no-such-server").isEmpty());
    }

    @Test
    public void testUpdateProject() throws Exception {
        dao.create(42, "upstream-server", 99, "aa-bb-cc-dd");
        dao.updateProject(42, 100);
        assertEquals(100, dao.getByLocalId(42).getLocalProjectId().intValue());
    }

    @Test
    public void testUpdateSyncDate() {
        dao.create(42, "upstream-server", 99, "aa-bb-cc-dd");
        Date firstDate = new Date();
        dao.updateSyncDate(42, firstDate);

        AoRepositoryMapping mapping = dao.getByLocalId(42);
        assertMapping(42, "upstream-server", 99, "aa-bb-cc-dd", mapping);
        assertThat(firstDate, closeTo(mapping.getInitialSyncDate(), 1, TimeUnit.SECONDS));
        assertThat(firstDate, closeTo(mapping.getLastSyncDate(), 1, TimeUnit.SECONDS));

        Date secondDate = new Date();
        dao.updateSyncDate(42, secondDate);
        mapping = dao.getByLocalId(42);
        assertMapping(42, "upstream-server", 99, "aa-bb-cc-dd", mapping);
        // MySQL timestamps have 1 second resolution
        assertThat(firstDate, closeTo(mapping.getInitialSyncDate(), 1, TimeUnit.SECONDS));
        assertThat(secondDate, closeTo(mapping.getLastSyncDate(), 1, TimeUnit.SECONDS));
    }

    private void assertMapping(int localId, String upstreamId, int localProjectId, String externalId,
                               AoRepositoryMapping mapping) {
        assertNotNull(mapping);
        assertEquals(Integer.valueOf(localId), mapping.getLocalId());
        assertEquals(upstreamId, mapping.getUpstreamId());
        assertEquals(Integer.valueOf(localProjectId), mapping.getLocalProjectId());
        assertEquals(externalId, mapping.getExternalId());
    }
}