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

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import org.jetbrains.annotations.NotNull;

import ru.yandex.qe.dispenser.domain.Entity;
import ru.yandex.qe.dispenser.domain.EntitySpec;
import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.Quota;
import ru.yandex.qe.dispenser.domain.dao.GenericDao;
import ru.yandex.qe.dispenser.domain.dao.KeyDao;
import ru.yandex.qe.dispenser.domain.support.EntityOperation;
import ru.yandex.qe.dispenser.domain.support.EntityRelease;
import ru.yandex.qe.dispenser.domain.support.EntityUsageDiff;
import ru.yandex.qe.dispenser.domain.support.QuotaDiff;
import ru.yandex.qe.dispenser.domain.util.Page;
import ru.yandex.qe.dispenser.domain.util.StreamUtils;

public interface EntityDao extends GenericDao<Entity, Long>, KeyDao<Entity, Entity.Key, Long> {
    default void doChanges(@NotNull final Collection<EntityOperation> operations) {
        final Set<Entity> allEntities = operations.stream().map(EntityOperation::getEntity).collect(Collectors.toSet());
        final Table<Entity, Project, Integer> allUsages = getUsages(allEntities);

        final BatchDiff batchDiff = new BatchDiff();
        for (final EntityOperation op : operations) {
            batchDiff.merge(EntityDaoUtils.processOperation(op, allUsages));
        }

        LOG.debug("Changing {} usages...", batchDiff.getUsageDiffs().size());
        changeUsages(batchDiff.getUsageDiffs());

        final Set<Entity> negativeEntities = Sets.difference(allEntities, allUsages.rowKeySet());
        LOG.debug("Cleaning usages of {} entities...", negativeEntities.size());
        cleanIfNeeded(negativeEntities);

        final Set<Entity> trashEntities = StreamUtils.instances(operations, EntityRelease.class)
                .map(EntityOperation::getEntity)
                .collect(Collectors.toSet());
        LOG.debug("Releasing {} entities...", trashEntities.size());
        deleteAll(trashEntities);
        LOG.debug("deleted {} entities", trashEntities.size());
        reshareQuota(batchDiff.getQuotaDiffs());
        LOG.debug("quota reshared");
    }

    @NotNull
    default Map<Entity.Key, Entity> readAllForUpdate(@NotNull final Collection<Entity.Key> keys) {
        return readAll(keys);
    }

    @NotNull
    default Map<Entity.Key, Entity> readPresentForUpdate(@NotNull final Collection<Entity.Key> keys) {
        return readPresent(keys);
    }

    @NotNull
    Set<Quota> reshareQuota(@NotNull Collection<QuotaDiff> quotasDiff);

    @NotNull
    Table<Entity, Project, Integer> getUsages(@NotNull Collection<Entity> entities);

    boolean changeUsages(@NotNull List<EntityUsageDiff> usageDiffs);

    boolean cleanIfNeeded(@NotNull Collection<Entity> entity);

    @NotNull
    @Deprecated
    Set<Entity> filter(@NotNull Collection<EntitySpec> specs, @NotNull EntityFilteringParams params);

    @Deprecated
    default Stream<Entity> filterStream(@NotNull Collection<EntitySpec> specs, @NotNull EntityFilteringParams params) {
        return filter(specs, params).stream();
    }

    @NotNull
    @Deprecated
    default Set<Entity> filter(@NotNull final EntitySpec spec, @NotNull final EntityFilteringParams params) {
        return filter(Collections.singletonList(spec), params);
    }

    @NotNull
    Page<Entity> filterPage(@NotNull final EntitySpec spec, @NotNull final EntityFilteringParams params);
}
