package com.atlassian.stash.internal.auth;

import com.atlassian.stash.internal.AbstractHibernateDao;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import static com.atlassian.stash.internal.HibernateUtils.initialize;
import static com.atlassian.stash.internal.HibernateUtils.initializeList;
import static com.atlassian.stash.internal.HibernateUtils.initializePage;

@Repository("rememberMeTokenDao")
public class HibernateRememberMeTokenDao extends AbstractHibernateDao<Long, InternalRememberMeToken> implements RememberMeTokenDao {

    @Autowired
    public HibernateRememberMeTokenDao(SessionFactory sessionFactory) {
        super(sessionFactory);
    }

    @Override
    public boolean claim(@Nonnull InternalRememberMeToken token) {
        int updated = session().createQuery("update InternalRememberMeToken " +
                "set expiryDate = :newExpiry, claimed = true, remoteAddress = :hostname " +
                "where series = :series and token = :token and claimed = false and expiryDate > :now")
                .setTimestamp("newExpiry", token.getExpiryDate())
                .setString("hostname", token.getRemoteAddress())
                .setString("series", token.getSeries())
                .setString("token", token.getToken())
                .setDate("now", new Date())
                .executeUpdate();

        if (updated > 1) {
            throw new DataIntegrityViolationException("more than one remember-me token was claimed");
        }
        return updated == 1;
    }

    @Override
    public int deleteExpiredTokens(long maxExpiredTime, @Nonnull TimeUnit timeUnit) {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.add(Calendar.SECOND, (int) -timeUnit.toSeconds(maxExpiredTime));
        Date maxExpiredDate = calendar.getTime();

        return session().createQuery("delete from InternalRememberMeToken where expiryDate < :expiryDate")
                .setTimestamp("expiryDate", maxExpiredDate)
                .executeUpdate();
    }

    @Override
    public int deleteForSeries(@Nonnull String series) {
        return session().createQuery("delete from InternalRememberMeToken where series = :series")
                .setString("series", series)
                .executeUpdate();
    }

    @Override
    public int deleteForUser(int userId) {
        return session().createQuery("delete from InternalRememberMeToken where user.id = :userId")
                .setInteger("userId", userId)
                .executeUpdate();
    }

    @Nonnull
    @Override
    public Page<InternalRememberMeToken> findAll(PageRequest pageRequest) {
        return initializePage(super.findAll(pageRequest));
    }

    @Nonnull
    @Override
    public Page<InternalRememberMeToken> findAll(PageRequest pageRequest, Predicate<? super InternalRememberMeToken> predicate) {
        return initializePage(super.findAll(pageRequest, predicate));
    }

    @Nullable
    @Override
    public InternalRememberMeToken findBySeriesAndToken(@Nonnull String series, @Nonnull String token) {
        return initialize((InternalRememberMeToken) session().createQuery(
                "from InternalRememberMeToken where series = :series and token = :token")
                .setString("series", series)
                .setString("token", token)
                .uniqueResult());
    }

    @Override
    public InternalRememberMeToken getById(Long id) {
        return initialize(super.getById(id));
    }

    @Nonnull
    @Override
    public List<InternalRememberMeToken> getByIds(@Nonnull Collection<Long> ids) {
        return initializeList(super.getByIds(ids));
    }

    @Override
    public InternalRememberMeToken loadById(Long id) {
        return initialize(super.loadById(id));
    }

    @Override
    public void refresh(InternalRememberMeToken entity) {
        super.refresh(entity);
        initialize(entity);
    }

    @Override
    public InternalRememberMeToken update(InternalRememberMeToken entity) {
        return initialize(super.update(entity));
    }

    @Override
    protected Iterable<Order> getImplicitOrder() {
        return Collections.singleton(Order.asc("id"));
    }
}
