package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.AbstractDaoTest;
import com.atlassian.bitbucket.mesh.dbunit.DataSet;
import com.atlassian.bitbucket.mesh.dbunit.DataSets;
import org.junit.Test;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;

@DataSet(DataSets.WORK_TREES)
public class JdbcWorkTreeStateDaoTest extends AbstractDaoTest {

    private static final Instant EXPIRY = Instant.ofEpochMilli(123456);
    private static final String REPOSITORY_ID = ManagedRepository.formatId(1, "123434", "12343254345");
    private static final WorkTreeState.Builder PARAMETERS = new WorkTreeState.Builder()
            .expiry(EXPIRY)
            .hasAlternates(true)
            .repositoryId(REPOSITORY_ID);

    @Resource
    private WorkTreeStateDao dao;

    @Test
    public void testCreate() {
        long createdId = dao.create(EXPIRY, true, REPOSITORY_ID);
        WorkTreeState expectedState = PARAMETERS.id(createdId).build();
        WorkTreeState state = dao.findById(createdId);

        assertEqualState(expectedState, state);
    }

    @Test
    public void testCreateMultiple() {
        long idOne = dao.create(EXPIRY, true, REPOSITORY_ID);

        long idTwo = dao.create(Instant.now(), false, REPOSITORY_ID);

        assertNotEquals("States should differ", dao.findById(idOne), dao.findById(idTwo));
    }

    @Test
    public void testCreateThrowsIfNoIdIsReturned() {
        //we cannot use the provided DAO as it will just work and not fail the way we need it to
        //the mock will not populate the keyHolder which is the error condition we're looking for
        JdbcWorkTreeStateDao failingDao = new JdbcWorkTreeStateDao(mock(JdbcTemplate.class));
        assertThrows("Database did not return a valid ID for work tree creation", DataIntegrityViolationException.class,
                () -> failingDao.create(EXPIRY, false, REPOSITORY_ID));
    }

    @Test
    public void testDeleteByIds() {
        List<Long> allIds = dao.getAllIds();

        dao.deleteByIds(Collections.singletonList(allIds.get(0)));

        List<Long> remaining = dao.getAllIds();
        assertThat(remaining, not(contains(allIds.get(0))));
    }

    @Test
    public void testDeleteById() {
        List<Long> allIds = dao.getAllIds();
        Long idToDelete = allIds.get(0);

        assertEquals(1, dao.deleteById(idToDelete));

        assertNull("Did not delete the work tree state", dao.findById(idToDelete));
        assertNotNull("Deleted a work tree that should not have a been removed", dao.findById(allIds.get(1)));
        assertNotNull("Deleted a work tree that should not have a been removed", dao.findById(allIds.get(2)));
    }

    @Test
    public void testFindById() {
        long id = dao.create(EXPIRY, true, REPOSITORY_ID);

        WorkTreeState actual = dao.findById(id);

        assertNotNull(actual);
        assertEqualState(PARAMETERS.id(id).build(), actual);
    }

    @Test
    public void testFindByRepository() {
        String repositoryId = "p/0001/h/123434/r/54321";

        List<WorkTreeState> states = dao.findByRepository(repositoryId);

        assertEquals("Wrong number of states returned", 2, states.size());
        assertThat(states.stream()
                .map(WorkTreeState::getRepositoryId)
                .collect(Collectors.toList()), contains(repositoryId, repositoryId));
    }

    @Test
    public void testGetAllIds() {
        List<Long> workTrees = dao.getAllIds();
        assertThat(workTrees, everyItem(greaterThanOrEqualTo(0L)));
    }

    private void assertEqualState(WorkTreeState expected, WorkTreeState actual) {
        assertEquals("id", expected.getId(), actual.getId());
        assertEquals("expiry", expected.getExpiry().toEpochMilli(), actual.getExpiry().toEpochMilli());
        assertEquals("hasAlternates", expected.hasAlternates(), actual.hasAlternates());
        assertEquals("repositoryId", expected.getRepositoryId(), actual.getRepositoryId());
    }
}