package ru.yandex.qe.dispenser.domain.dao.quota;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.Quota;
import ru.yandex.qe.dispenser.domain.QuotaSpec;
import ru.yandex.qe.dispenser.domain.QuotaView;
import ru.yandex.qe.dispenser.domain.Resource;
import ru.yandex.qe.dispenser.domain.dao.GenericDao;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.domain.support.QuotaDiff;

public interface QuotaDao extends GenericDao<Quota, Long>, QuotaReader {
    Logger LOG = LoggerFactory.getLogger(QuotaDao.class);

    @NotNull
    Set<Quota> getQuotasForUpdate(@NotNull final Collection<Quota.Key> quotaKeys);

    @NotNull
    Set<Quota> getProjectsQuotasForUpdate(@NotNull final Collection<Project> projects);

    @NotNull
    Set<Quota> getQuotasForUpdate(final @NotNull QuotaSpec quotaSpec);

    @NotNull
    default Set<Quota> finalizeAndChangeAll(@NotNull final Collection<QuotaDiff> quotaDiffs) {
        Collection<QuotaDiff> finalQuotaDiffs = quotaDiffs;
        if (Session.TRY_ACQUIRE.get()) {
            finalQuotaDiffs = QuotaUtils.expandToReal(quotaDiffs);
        }
        LOG.debug("Changing {} quotas...", finalQuotaDiffs.size());
        return changeAll(finalQuotaDiffs);
    }

    // TODO: return value
    default void createAll(@NotNull final Collection<Quota> quotas) {
        quotas.stream().sorted().forEach(this::create);
    }

    @NotNull
    Set<Quota> changeAll(@NotNull Collection<QuotaDiff> causes);

    void createZeroQuotasFor(@NotNull QuotaSpec quotaSpec);

    void createZeroQuotasFor(@NotNull Collection<Project> projects);

    void updateQuotaSegments(@NotNull final Collection<Resource> resources);

    void setLastOverquotingTs(@NotNull Quota quota, @Nullable Long ts);

    void applyChanges(@NotNull final Map<Quota.Key, Long> max, @NotNull final Map<Quota.Key, Long> ownActual,
                      @NotNull final Map<Quota.Key, Long> ownMax);

    default Collection<QuotaView> checkAndApplyChanges(@NotNull final Quota.ChangeHolder newValues) {
        final List<QuotaView> aggregatedQuotas = QuotaUtils.lockAndCheckValues(this, newValues);
        applyChanges(newValues.getMax(), newValues.getOwnActual(), newValues.getOwnMax());
        return aggregatedQuotas;
    }

    default Collection<QuotaView> checkAndApplyChanges(@NotNull final Quota.ChangeHolder newValues,
                                                       @NotNull final Set<Quota> lockedQuotas) {
        final List<QuotaView> aggregatedQuotas = QuotaUtils.checkValues(this, newValues);
        applyChanges(newValues.getMax(), newValues.getOwnActual(), newValues.getOwnMax());
        return aggregatedQuotas;
    }
}
