package com.atlassian.stash.internal.auth;

import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.annotation.DataSet;
import com.atlassian.stash.internal.AbstractDaoTest;
import com.atlassian.stash.internal.DataSets;
import com.atlassian.stash.internal.user.HibernateStashUserDao;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.stash.internal.user.StashUserDao;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

@DataSet({DataSets.USERS, DataSets.REMEMBER_ME_TOKENS})
public class HibernateRememberMeTokenDaoTest extends AbstractDaoTest {

    private static final String HOSTNAME = "example.host.name";

    @Dao
    private HibernateRememberMeTokenDao dao;
    @Dao(HibernateStashUserDao.class)
    private StashUserDao userDao;

    @Test
    public void testClaim() {
        InternalRememberMeToken token = dao.getById(3L);
        assertTrue(doClaim(token));

        evict(token, 3L);

        // refetch the token
        token = dao.getById(3L);
        assertEquals(3L, token.getId());
        assertEquals("series-a", token.getSeries());
        assertEquals("current-token", token.getToken());
        assertTrue(token.isClaimed());
        assertFalse(token.isExpired());
        assertEquals(HOSTNAME, token.getRemoteAddress());
    }

    @Test
    public void testClaimAlreadyClaimedToken() {
        InternalRememberMeToken token = dao.getById(2L);

        // sanity check
        assertTrue(token.isClaimed());

        assertFalse(dao.claim(token));
    }

    @Test
    public void testClaimExpiredToken() {
        InternalRememberMeToken token = dao.getById(5L);

        // sanity check
        assertFalse(token.isClaimed());
        assertTrue(token.isExpired());

        assertFalse(doClaim(token));
    }

    @Test
    public void testClaimDeletedToken() {
        InternalRememberMeToken token = dao.getById(3L);

        // sanity check
        assertFalse(token.isClaimed());
        dao.deleteById(3L);

        assertFalse(doClaim(token));
    }

    @Test
    public void testDeleteExpiredToken() {
        InternalApplicationUser user = userDao.getById(3);
        InternalRememberMeToken token = dao.create(new InternalRememberMeToken.Builder()
                .expiresAfter(-10, TimeUnit.MINUTES)
                .series("magic-series")
                .token("magic-token")
                .user(user)
                .build());

        // 2 expired tokens in the test data, but the token we just created should survive
        assertEquals(2, dao.deleteExpiredTokens(602, TimeUnit.SECONDS));
        // the new token that we added should now be deleted
        assertEquals(1, dao.deleteExpiredTokens(599, TimeUnit.SECONDS));

        evict(token, token.getId());
        assertNull(dao.getById(token.getId()));
    }

    @Test
    public void testDeleteForSeries() {
        Page<InternalRememberMeToken> before = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(3, dao.deleteForSeries("series-a")); // has three tokens

        Page<InternalRememberMeToken> after = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(before.getSize() - 3, after.getSize());

        for (InternalRememberMeToken token : after.getValues()) {
            assertNotEquals("series-a", token.getSeries());
        }
    }

    @Test
    public void testDeleteForUnknownSeries() {
        Page<InternalRememberMeToken> before = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(0, dao.deleteForSeries("unknown"));

        Page<InternalRememberMeToken> after = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(before.getSize(), after.getSize());
    }

    @Test
    public void testDeleteForUser() {
        Page<InternalRememberMeToken> before = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(4, dao.deleteForUser(1));

        Page<InternalRememberMeToken> after = dao.findAll(PageUtils.newRequest(0, 1000));
        assertEquals(before.getSize() - 4, after.getSize());

        for (InternalRememberMeToken token : after.getValues()) {
            assertNotEquals(1, token.getUser().getId());
        }
    }

    @Test
    public void testFindBySeriesAndToken() {
        InternalRememberMeToken token = dao.findBySeriesAndToken("series-a", "current-token");

        assertNotNull(token);
        assertEquals(3L, token.getId());
        assertEquals("series-a", token.getSeries());
        assertEquals("current-token", token.getToken());
        assertFalse(token.isClaimed());
        assertFalse(token.isExpired());
        assertEquals(1, token.getUser().getId());
    }

    @Test
    public void testFindBySeriesAndTokenNonExistingSeries() {
        InternalRememberMeToken token = dao.findBySeriesAndToken("series-z", "current-token");

        assertNull(token);
    }

    @Test
    public void testFindBySeriesAndTokenNonExistingValue() {
        InternalRememberMeToken token = dao.findBySeriesAndToken("series-a", "other-token");

        assertNull(token);
    }


    private boolean doClaim(InternalRememberMeToken token) {
        return dao.claim(new InternalRememberMeToken.Builder(token)
                .expiresAfter(50, TimeUnit.SECONDS)
                .remoteAddress(HOSTNAME)
                .build());
    }
}