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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

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

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.Resource;
import ru.yandex.qe.dispenser.domain.ResourceSegmentation;
import ru.yandex.qe.dispenser.domain.Segment;
import ru.yandex.qe.dispenser.domain.dao.project.ProjectDao;
import ru.yandex.qe.dispenser.domain.dao.quota.spec.QuotaSpecDao;
import ru.yandex.qe.dispenser.domain.dao.resource.segmentation.ResourceSegmentationDao;
import ru.yandex.qe.dispenser.domain.dao.segment.SegmentDao;
import ru.yandex.qe.dispenser.domain.util.MoreCollectors;

interface IntegratedQuotaDao extends QuotaDao {

    @Override
    default void createZeroQuotasFor(@NotNull final QuotaSpec quotaSpec) {

        final Resource resource = quotaSpec.getResource();
        final List<Set<Segment>> segmentSets = getAllSegmentsCombinations(Collections.singleton(resource)).get(resource);

        final Collection<Quota.Key> quotaKeys = QuotaUtils.getQuotaKeys(Collections.singleton(quotaSpec), getDirectProjectDao().getAll(), segmentSets);

        createZeroQuotas(quotaKeys);
    }

    @Override
    default void createZeroQuotasFor(@NotNull final Collection<Project> projects) {
        if (projects.isEmpty()) {
            return;
        }

        final Map<Resource, Collection<QuotaSpec>> quotaSpecByResource = getDirectQuotaSpecDao().getAll().stream()
                .collect(Collectors.groupingBy(QuotaSpec::getResource, Collectors.toCollection(ArrayList::new)));

        final Collection<Quota.Key> quotaKeys = getValidQuotaKeys(projects, quotaSpecByResource);

        createZeroQuotas(quotaKeys);
    }

    @NotNull
    default Set<Quota.Key> getValidQuotaKeys(@NotNull final Collection<Project> projects,
                                             @NotNull final Map<Resource, Collection<QuotaSpec>> quotaSpecByResource) {

        final Map<Resource, List<Set<Segment>>> segmentCombinationsByResource = getAllSegmentsCombinations(quotaSpecByResource.keySet());

        return quotaSpecByResource.keySet()
                .stream()
                .flatMap(res -> {
                    final List<Set<Segment>> segmentSets = segmentCombinationsByResource.get(res);
                    final Collection<Quota.Key> resourceQuotaKeys = QuotaUtils.getQuotaKeys(quotaSpecByResource.get(res), projects, segmentSets);
                    return resourceQuotaKeys.stream();
                })
                .collect(Collectors.toSet());
    }

    default Map<Resource, List<Set<Segment>>> getAllSegmentsCombinations(@NotNull final Collection<Resource> resources) {
        final SegmentDao segmentDao = getSegmentDao();

        final Map<Resource, List<Set<Segment>>> segmentationsSegmentsByResource = getResourceSegmentationDao().getResourceSegmentations(resources).stream()
                .collect(MoreCollectors.toMultiValueMap(ResourceSegmentation::getResource, rs -> Sets.union(segmentDao.get(rs.getSegmentation()), Collections.singleton(Segment.totalOf(rs.getSegmentation())))));

        final Map<Resource, List<Set<Segment>>> result = segmentationsSegmentsByResource.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e ->
                        Sets.cartesianProduct(e.getValue())
                                .stream()
                                .map(HashSet::new)
                                .collect(Collectors.toList())
                ));


        resources.stream()
                .filter(r -> !result.containsKey(r))
                .forEach(r -> result.put(r, Collections.singletonList(Collections.emptySet())));

        return result;
    }

    default void createZeroQuotas(@NotNull final Collection<Quota.Key> keys) {
        final Set<Quota> quotas = keys.stream()
                .map(Quota::noQuota)
                .collect(Collectors.toSet());

        createAll(quotas);
    }

    @Override
    default void updateQuotaSegments(@NotNull final Collection<Resource> resources) {
        final Map<Resource, Collection<QuotaSpec>> specsByResource = getDirectQuotaSpecDao().getByResources(resources).asMap();

        final Map<Quota.Key, Quota> existingQuotas = getQuotasByResources(resources).stream()
                .collect(Collectors.toMap(Quota::getKey, Function.identity()));

        final Set<Quota.Key> existingKeys = existingQuotas.keySet();

        final Set<Quota.Key> validKeys = getValidQuotaKeys(getDirectProjectDao().getAll(), specsByResource);

        final Sets.SetView<Quota.Key> keysToRemove = Sets.difference(existingKeys, validKeys);
        final Sets.SetView<Quota.Key> keysToCreate = Sets.difference(validKeys, existingKeys);

        if (!keysToRemove.isEmpty()) {
            deleteAll(keysToRemove.stream()
                    .map(existingQuotas::get)
                    .collect(Collectors.toSet())
            );
        }

        if (!keysToCreate.isEmpty()) {
            createZeroQuotas(keysToCreate);
        }
    }

    @NotNull
    ProjectDao getDirectProjectDao();

    @NotNull
    QuotaSpecDao getDirectQuotaSpecDao();

    @NotNull
    ResourceSegmentationDao getResourceSegmentationDao();

    @NotNull
    SegmentDao getSegmentDao();
}
