package com.atlassian.stash.internal.pull;

import com.atlassian.bitbucket.pull.PullRequestParticipantStatus;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.hibernate.HibernatePageUtils;
import com.atlassian.stash.internal.participant.AbstractHibernateParticipantDao;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Optional;

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

@Repository("pullRequestParticipantDao")
public class HibernatePullRequestParticipantDao
        extends AbstractHibernateParticipantDao<InternalPullRequestParticipant>
        implements PullRequestParticipantDao {

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

    @Nonnull
    @Override
    public Page<InternalPullRequestParticipant> findByPullRequest(long pullRequestId, @Nonnull PageRequest pageRequest) {
        Criteria criteria = session().createCriteria(InternalPullRequestParticipant.class)
                .add(Restrictions.eq("pullRequest.id", pullRequestId));

        return initializePage(pageCriteria(applyImplicitOrder(criteria), pageRequest));
    }

    @Nullable
    @Override
    public InternalPullRequestParticipant findByPullRequestAndUser(long pullRequestId, int userId) {
        Criteria criteria = session().createCriteria(InternalPullRequestParticipant.class)
                .add(Restrictions.eq("pullRequest.id", pullRequestId))
                .add(Restrictions.eq("user.id", userId));

        return initialize((InternalPullRequestParticipant) criteria.uniqueResult());
    }

    @Nonnull
    @Override
    public Page<InternalApplicationUser> searchUsers(@Nonnull PullRequestParticipantSearchCriteria searchCriteria,
                                                     @Nonnull PageRequest pageRequest) {
        StringBuilder querySql = new StringBuilder();
        querySql.append("select user ")
                .append("from InternalNormalUser user where user.id in (")
                .append("select distinct participant.user.id from InternalPullRequestParticipant as participant");

        Optional<InternalRepository> fromRepository = searchCriteria.getFromRepository();
        Optional<InternalRepository> toRepository = searchCriteria.getToRepository();

        if (fromRepository.isPresent() || toRepository.isPresent()) {
            querySql.append(" inner join participant.pullRequest as pull");

            fromRepository.ifPresent(repository ->
                    querySql.append(" with pull.fromRef.repository.id = :fromRepoId"));

            toRepository.ifPresent(repository -> {
                if (fromRepository.isPresent()) {
                    querySql.append(" and");
                } else {
                    querySql.append(" with");
                }
                querySql.append(" pull.toRef.repository.id  = :toRepoId");
            });
        }

        searchCriteria.getRole().ifPresent(role ->
                querySql.append(" where participant.role = :role"));

        querySql.append(" )");
        querySql.append(" order by user.username asc");

        Query query = session().createQuery(querySql.toString());

        fromRepository.ifPresent(repository ->
                query.setInteger("fromRepoId", repository.getId()));

        toRepository.ifPresent(repository ->
                query.setInteger("toRepoId", repository.getId()));

        searchCriteria.getRole().ifPresent(role ->
                query.setParameter("role", role));

        query.setCacheable(true);
        query.setCacheRegion(ApplicationConstants.QUERY_CACHE_PULL_REQUEST_PARTICIPANT_USERS);

        return initializePage(HibernatePageUtils.pageQuery(query, pageRequest));
    }

    @Override
    public int resetStatusByPullRequest(long pullRequestId) {
        return session().createQuery("update InternalPullRequestParticipant set status = :current " +
                "where pullRequest.id = :pullRequest and status = :previous")
                .setParameter("current", PullRequestParticipantStatus.UNAPPROVED)
                .setLong("pullRequest", pullRequestId)
                .setParameter("previous", PullRequestParticipantStatus.NEEDS_WORK)
                .executeUpdate();
    }

    @Override
    protected Iterable<Order> getImplicitOrder() {
        return Arrays.asList(Order.asc("role"), Order.asc("id"));
    }
}
