package com.atlassian.stash.internal.watcher;

import com.atlassian.stash.internal.AbstractHibernateDao;
import com.atlassian.stash.internal.InternalConverter;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.watcher.Watchable;
import com.google.common.collect.ImmutableSet;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
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.Collections;
import java.util.Set;

import static com.atlassian.stash.internal.HibernateUtils.*;

@Repository("watcherDao")
public class HibernateWatcherDao extends AbstractHibernateDao<Long, InternalWatcher> implements WatcherDao {

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

    @Override
    public long countWatchers(@Nonnull Watchable watchable) {
        return count(particularWatchable(watchable).getExecutableCriteria(session()));
    }

    @Override
    public InternalWatcher create(InternalWatcher entity) {
        InternalWatcher watcher = super.create(entity);

        InternalWatchable watchable = watcher.getWatchable();
        watchable.addWatcher(watcher);
        session().update(watchable);

        return watcher;
    }

    @Override
    public void delete(final InternalWatcher entity) {
        InternalWatchable watchable = entity.getWatchable();
        watchable.removeWatcher(entity);
        session().update(watchable);

        super.delete(entity);
    }

    @Nonnull
    @Override
    @SuppressWarnings("unchecked")
    public Set<InternalWatcher> findByWatchable(@Nonnull Watchable watchable) {
        Criteria criteria = particularWatchable(watchable).getExecutableCriteria(session());

        return ImmutableSet.copyOf(initializeList(criteria.list()));
    }

    @Nullable
    @Override
    public InternalWatcher findByWatchableAndUser(@Nonnull Watchable watchable, @Nonnull ApplicationUser user) {
        return initialize((InternalWatcher) particularWatchableAndUser(watchable, user)
                .getExecutableCriteria(session()).uniqueResult());
    }

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

    @Override
    public InternalWatcher update(InternalWatcher entity) {
        throw new UnsupportedOperationException("Watchers are immutable and cannot be updated.");
    }

    @Override
    protected Iterable<Order> getImplicitOrder() {
        return Collections.singleton(Order.desc("id")); //There's really no useful ordering for watchers
    }

    private DetachedCriteria particularWatchable(Watchable watchable) {
        return DetachedCriteria.forClass(InternalWatcher.class).add(Restrictions.eq("watchable", watchable));
    }

    private DetachedCriteria particularWatchableAndUser(Watchable watchable, ApplicationUser user) {
        return particularWatchable(watchable).add(Restrictions.eq("user", InternalConverter.convertToInternalUser(user)));
    }
}
