package com.atlassian.bitbucket.internal.key.ssh.dao;

import com.atlassian.activeobjects.test.TestActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDaoTest;
import com.atlassian.bitbucket.ssh.util.KeyUtils;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.google.common.collect.Iterables;
import net.java.ao.DBParam;
import net.java.ao.EntityManager;
import net.java.ao.test.jdbc.Data;
import net.java.ao.test.jdbc.DatabaseUpdater;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;

import static com.atlassian.bitbucket.internal.key.ssh.dao.Keys.*;
import static com.atlassian.bitbucket.internal.key.ssh.dao.SshKeyMatcher.key;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@Data(AoSshKeyDaoTest.TestDataUpdater.class)
public class AoSshKeyDaoTest extends AbstractAoDaoTest {
    public static final Matcher<AoSshKey> CONSISTENT_WITH_FIRST_USER_KEY =
            key(PUBLIC_KEY_1, PUBLIC_KEY_1_MD5, PUBLIC_KEY_LABEL, 1, 1);
    public static final Matcher<AoSshKey> CONSISTENT_WITH_SECOND_USER_KEY =
            key(PUBLIC_KEY_2, PUBLIC_KEY_2_MD5, PUBLIC_KEY_LABEL, 2, 2);

    private ApplicationUser user1;
    private ApplicationUser user2;
    private ApplicationUser user3;
    private AoSshKeyDao keyDao;

    public AoSshKeyDaoTest() {
        super(AoSshKey.class);
    }

    @Before
    public void setUp() throws Exception {
        keyDao = new AoSshKeyDao(new TestActiveObjects(entityManager));

        user1 = mockUser(1, "user1");
        user2 = mockUser(2, "user1");
        user3 = mockUser(3, "user1");
    }

    @Test
    public void getById() {
        assertThat(keyDao.getById(1), is(CONSISTENT_WITH_FIRST_USER_KEY));
        assertThat(keyDao.getById(2), is(CONSISTENT_WITH_SECOND_USER_KEY));
    }

    @Test
    public void getByNegativeId() {
        assertNull(keyDao.getById(-1));
    }

    @Test
    public void getByUnknownId() {
        assertNull(keyDao.getById(99));
    }

    //-------------------------------------------------------------------------------
    // SSH key access entry retrieval by public key
    //-------------------------------------------------------------------------------

    @Test
    public void getByPublicKey() {
        assertThat(keyDao.getByPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_1)), is(CONSISTENT_WITH_FIRST_USER_KEY));
        assertThat(keyDao.getByPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_2)), is(CONSISTENT_WITH_SECOND_USER_KEY));
        assertNull(keyDao.getByPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_5)));
    }

    @Test
    public void getByUserPublicKey() {
        assertThat(keyDao.getByUserPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_2)), is(CONSISTENT_WITH_SECOND_USER_KEY));
    }

    @Test
    public void getByUserPublicKeyForContextKey() {
        // public key 3 is an SSH key associated with an anon context. This should not be returned for getByUserPublicKey
        assertNull(keyDao.getByUserPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_3)));
    }

    @Test(expected = NullPointerException.class)
    public void getByNullUserPublicKey() {
        //noinspection ConstantConditions
        keyDao.getByUserPublicKey(null);
    }

    @Test
    public void getByUnknownUserPublicKey() {
        assertNull(keyDao.getByUserPublicKey(KeyUtils.getPublicKey(PUBLIC_KEY_5)));
    }

    //-------------------------------------------------------------------------------
    // SSH key access entry retrieval by user id
    //-------------------------------------------------------------------------------

    @Test
    public void findByUser() {
        Page<AoSshKey> sshKeys = keyDao.findByUser(1, new PageRequestImpl(0, 10));
        assertEquals(1, sshKeys.getSize());
        assertEquals(0, sshKeys.getStart());
        assertTrue("Expected isLastPage", sshKeys.getIsLastPage());
        Iterable<AoSshKey> values = sshKeys.getValues();
        assertEquals(1, Iterables.size(values));
        AoSshKey firstValue = Iterables.getFirst(values, null);
        assertThat(firstValue, is(CONSISTENT_WITH_FIRST_USER_KEY));
    }

    @Test
    public void existsForUser() {
        // User id 1 has ssh keys
        assertTrue(keyDao.existsForUser(1));
        // Unknown id, doesn't have ssh keys
        assertFalse(keyDao.existsForUser(3));
        assertFalse(keyDao.existsForUser(-1));

        // Add in an ssh key, then check they have one
        keyDao.create(user3, PUBLIC_KEY_5, null);
        assertTrue(keyDao.existsForUser(3));
    }

    //-------------------------------------------------------------------------------
    // Adding SSH key access entries for users
    //-------------------------------------------------------------------------------

    @Test
    public void createForUser() {
        assertThat(keyDao.create(user3, PUBLIC_KEY_5, null),
                   is(key(PUBLIC_KEY_5, PUBLIC_KEY_5_MD5, null, 3, 3)));
    }

    @Test
    public void createForUserWithNewlines() {
        assertThat(keyDao.create(user3, PUBLIC_KEY_3_WITH_TABS_AND_NEWLINES, null),
                   is(key(PUBLIC_KEY_3_WITH_TABS_AND_NEWLINES, PUBLIC_KEY_3_MD5, null, 3, 3)));
    }

    @Test
    public void createForUserWithLongLabelTruncatesLabel() {
        assertThat(keyDao.create(user3, PUBLIC_KEY_4_WITH_LONG_LABEL, LONG_LABEL),
                   is(key(PUBLIC_KEY_4_WITH_LONG_LABEL, PUBLIC_KEY_4_MD5, LONG_LABEL.substring(0, 255), 3, 3)));
    }

    @Test
    public void createForUserNullCreator() {
        assertThat(keyDao.create(user3, PUBLIC_KEY_5, null),
                is(key(PUBLIC_KEY_5, PUBLIC_KEY_5_MD5, null, 3, null)));
    }

    @Test
    public void createSecondKeyForUser() {
        // User 2 already has a key (PUBLIC_KEY_1)
        assertThat(keyDao.create(user2, PUBLIC_KEY_5, null),
                is(key(PUBLIC_KEY_5, PUBLIC_KEY_5_MD5, null, 2, 2)));
    }

    @Test(expected = NullPointerException.class)
    public void createForUserWithNullKey() {
        //noinspection ConstantConditions
        keyDao.create(user1, null, null);
    }

    /**
     * Tests adding of a duplicate key. This is allowed at the DAO level, but disallowed at the service level.
     */
    @Test
    public void createDuplicateKeyForUser() {
        // PUBLIC_KEY_2 already assigned to user 1
        assertThat(keyDao.create(user3, PUBLIC_KEY_2, PUBLIC_KEY_LABEL),
                is(key(PUBLIC_KEY_2, PUBLIC_KEY_2_MD5, PUBLIC_KEY_LABEL, 3, 3)));
    }

    @Test
    public void existsForAnyUser() {
        assertTrue(keyDao.existsForAnyUser(KeyUtils.getPublicKey(PUBLIC_KEY_2)));
        assertTrue(keyDao.existsForAnyUser(KeyUtils.getPublicKey(PUBLIC_KEY_1)));
        assertFalse(keyDao.existsForAnyUser(KeyUtils.getPublicKey(PUBLIC_KEY_3)));
        assertFalse(keyDao.existsForAnyUser(KeyUtils.getPublicKey(PUBLIC_KEY_4)));
        assertFalse(keyDao.existsForAnyUser(KeyUtils.getPublicKey(PUBLIC_KEY_5)));
    }

    @Test
    public void delete() {
        keyDao.create(user2, PUBLIC_KEY_5, null);
        Page<AoSshKey> keys = keyDao.findByUser(2, new PageRequestImpl(0, 10));
        assertEquals("Start with two keys", 2, keys.getSize());

        // remove first key
        keyDao.delete(Iterables.getFirst(keys.getValues(), null));

        // test first key is gone
        assertNull(keyDao.getById(Iterables.getFirst(keys.getValues(), null).getId()));

        // test other key untouched
        assertEquals("Have one key left", 1, keyDao.findByUser(2, new PageRequestImpl(0, 10)).getSize());
    }

    @Test
    public void deleteAllForUser() {
        keyDao.create(user2, PUBLIC_KEY_2, null);
        keyDao.create(user2, PUBLIC_KEY_3, null);
        keyDao.deleteAllForUser(2);
        assertEquals(0, keyDao.findByUser(2, new PageRequestImpl(0, 10)).getSize());
    }

    @Test
    public void deleteAllForUnknownUser() {
        keyDao.deleteAllForUser(6); // should do nothing

        // make sure existing keys are still there
        assertEquals(1, keyDao.findByUser(1, new PageRequestImpl(0, 10)).getSize());
        assertEquals(1, keyDao.findByUser(2, new PageRequestImpl(0, 10)).getSize());
    }

    //-------------------------------------------------------------------------------
    // Helper classes and/or methods
    //-------------------------------------------------------------------------------

    private ApplicationUser mockUser(int id, String username) {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getId()).thenReturn(id);
        when(user.getName()).thenReturn(username);
        return user;
    }

    public static class TestDataUpdater implements DatabaseUpdater {
        @Override
        @SuppressWarnings("unchecked")
        public void update(EntityManager entityManager) throws Exception {
            entityManager.migrate(AoSshKey.class);
            // create two user keys
            createKey(entityManager, PUBLIC_KEY_1, PUBLIC_KEY_1_MD5, PUBLIC_KEY_LABEL, 1, 1);
            createKey(entityManager, PUBLIC_KEY_2, PUBLIC_KEY_2_MD5, PUBLIC_KEY_LABEL, 2, 2);
        }

        private AoSshKey createKey(EntityManager entityManager, String keyText, String md5, String label,
                                   Integer userId, Integer creatorId) throws Exception {
            return entityManager.create(AoSshKey.class,
                    new DBParam(AoSshKey.COLUMN_USER_ID, userId),
                    new DBParam(AoSshKey.COLUMN_KEY_TEXT, keyText),
                    new DBParam(AoSshKey.COLUMN_KEY_MD5, md5),
                    new DBParam(AoSshKey.COLUMN_LABEL, label));
        }
    }
}
