package ru.yandex.qe.dispenser.ws.param;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.QueryParam;

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

import ru.yandex.qe.dispenser.api.v1.DiOrder;
import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.Project;
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.Segment;
import ru.yandex.qe.dispenser.domain.Segmentation;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.dao.quota.QuotaReader;
import ru.yandex.qe.dispenser.domain.dao.resource.ResourceReader;
import ru.yandex.qe.dispenser.domain.dao.resource.segmentation.ResourceSegmentationReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.ws.v3.QuotaFilterParamsImpl;

public class QuotaGetParams {
    public static final String RESOURCE_PARAM = "resource";
    public static final String ENTITY_SPEC_PARAM = "entitySpec";
    public static final String SEGMENT_PARAM = "segment";
    public static final String SERVICE_PARAM = "service";
    public static final String PROJECT_PARAM = "project";
    public static final String MEMBER_PARAM = "member";
    public static final String LEAF_PARAM = "leaf";
    public static final String ORDER_PARAM = "order";

    @QueryParam(RESOURCE_PARAM)
    List<ResourceFilterParam> resourceParams;
    @QueryParam(ENTITY_SPEC_PARAM)
    List<EntitySpecFilterParam> entitySpecParams;
    @QueryParam(SEGMENT_PARAM)
    Set<String> segmentKeys;
    @QueryParam(SERVICE_PARAM)
    Set<Service> services;

    @QueryParam(PROJECT_PARAM)
    Set<Project> projects;
    @QueryParam(MEMBER_PARAM)
    Set<Person> members;
    @QueryParam(LEAF_PARAM)
    @DefaultValue("false")
    boolean leafProjects;

    @QueryParam(ORDER_PARAM)
    DiOrder order;

    public QuotaGetParams(final List<ResourceFilterParam> resourceParams,
                          final List<EntitySpecFilterParam> entitySpecParams,
                          final Set<String> segmentKeys,
                          final Set<Service> services,
                          final Set<Project> projects,
                          final Set<Person> members,
                          final boolean leafProjects,
                          final DiOrder order) {
        this.resourceParams = resourceParams;
        this.entitySpecParams = entitySpecParams;
        this.segmentKeys = segmentKeys;
        this.services = services;
        this.projects = projects;
        this.members = members;
        this.leafProjects = leafProjects;
        this.order = order;
    }

    @NotNull
    public Stream<QuotaView> getQuotaStream() {
        final Set<Resource> resources = getResources();
        final Set<Project> membersProjects = getMembersProjects().orElse(Collections.emptySet());
        final Set<Segment> segments = getSegments();

        Stream<QuotaView> quotaStream = getQuotas(resources)
                .stream()
                .filter(q -> q.getProject().isReal()); // don't return quotas for personal projects

        if (!projects.isEmpty()) {
            quotaStream = quotaStream.filter(q -> projects.contains(q.getProject()));
        }
        if (!membersProjects.isEmpty()) {
            quotaStream = quotaStream.filter(q -> membersProjects.contains(q.getProject()));
        }
        if (leafProjects) {
            quotaStream = quotaStream.filter(q -> q.getProject().isRealLeaf());
        }

        if (!segments.isEmpty()) {
            quotaStream = quotaStream.filter(q -> q.getSegments().containsAll(segments));
        }

        final List<Comparator<QuotaView>> quotaComparators = new ArrayList<>();

        if (order != null) {
            if (resources.size() > 1) {
                throw new IllegalArgumentException("Can't order quotas for more then one resource: " + resources);
            }
            quotaComparators.add(order.comparing(QuotaView::getTotalActual));
        }

        final Optional<Comparator<QuotaView>> comparator = quotaComparators.stream()
                .reduce(Comparator::thenComparing);

        if (comparator.isPresent()) {
            quotaStream = quotaStream.sorted(comparator.get());
        }

        return quotaStream;
    }

    @NotNull
    public Set<Resource> getResources() {
        return getResourcesOptional().orElse(Collections.emptySet());
    }

    @NotNull
    public Optional<Set<Resource>> getResourcesOptional() {

        if (!resourceParams.isEmpty()) {
            return Optional.of(resourceParams.stream()
                    .map(ResourceFilterParam::get)
                    .collect(Collectors.toSet()));
        }

        if (!entitySpecParams.isEmpty()) {
            return Optional.of(entitySpecParams.stream()
                    .flatMap(specParam -> specParam.get().getResources().stream())
                    .collect(Collectors.toSet()));
        }

        if (!services.isEmpty()) {
            final ResourceReader resourceReader = Hierarchy.get().getResourceReader();
            return Optional.of(services.stream()
                    .flatMap(service -> resourceReader.getByService(service).stream())
                    .collect(Collectors.toSet()));
        }

        return Optional.empty();
    }

    @NotNull
    private Optional<Set<Project>> getMembersProjects() {
        if (members.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(members.stream()
                .flatMap(m -> Hierarchy.get().getMemberRealLeafProjects(m).stream())
                .collect(Collectors.toSet()));
    }

    @NotNull
    private Set<Segment> getSegments() {
        return segmentKeys.stream()
                .map(key -> Hierarchy.get().getSegmentReader().read(key))
                .collect(Collectors.toSet());
    }

    @NotNull
    private Collection<QuotaView> getQuotas(@NotNull final Collection<Resource> resources) {
        final Set<QuotaView> quotas;
        if (!resources.isEmpty()) {
            quotas = Hierarchy.get().getQuotaCache().getQuotasByResources(resources);
        } else {
            final Set<Project> allKnownProjects = Hierarchy.get().getProjectReader().getAll();
            quotas = Hierarchy.get().getQuotaCache().getQuotas(allKnownProjects);
        }
        return quotas;
    }

    public Optional<QuotaReader.QuotaFilterParams> toQuotaFilterParams() {
        final Optional<Set<Resource>> resources = getResourcesOptional();

        final Optional<Set<QuotaSpec>> quotaSpecs = resources.map(rs -> Sets.newHashSet(Hierarchy.get().getQuotaSpecReader()
                .getByResources(rs)
                .values()));

        if (quotaSpecs.isPresent() && quotaSpecs.get().isEmpty()) {
            return Optional.empty();
        }

        Set<QuotaSpec> resultQuotaSpecs = quotaSpecs.orElse(Collections.emptySet());

        final Optional<Set<Project>> projectsOptional = projects.isEmpty() ? Optional.empty() : Optional.of(projects);

        final Optional<Set<Project>> requestedProjects = Stream.of(projectsOptional, getMembersProjects())
                .filter(Optional::isPresent)
                .reduce(Optional.empty(), (acc, o2) -> {
                    if (!acc.isPresent()) {
                        return Optional.of(new HashSet<>(o2.get()));
                    }
                    acc.get().addAll(o2.get());
                    return acc;
                });

        if (requestedProjects.isPresent() && requestedProjects.get().isEmpty()) {
            return Optional.empty();
        }

        final Optional<Predicate<Project>> projectFilter = leafProjects ? Optional.of(Project::isRealLeaf) : Optional.empty();

        final Function<Set<Project>, Optional<Set<Project>>> filterTransformer2 =
                (projects) -> projectFilter.map(f -> projects.stream().filter(f).filter(Project::isReal).collect(Collectors.toSet()));

        final Set<Project> resultProjects = requestedProjects
                .map(projects -> filterTransformer2.apply(projects).orElse(projects)
                ).orElseGet(
                        () -> filterTransformer2.apply(Hierarchy.get().getProjectReader().getAll())
                                .orElse(Collections.emptySet())
                );

        final Set<Segment> segments = getSegments();

        final Set<Segmentation> querySegmentations = segments.stream()
                .map(Segment::getSegmentation)
                .collect(Collectors.toSet());

        if (querySegmentations.size() != segments.size()) {
            return Optional.empty();
        }

        if (querySegmentations.size() > 2) {
            throw new IllegalArgumentException("Can't filter by more than 2 segmentation");
        }

        if (!segments.isEmpty()) {

            final ResourceSegmentationReader resourceSegmentationReader = Hierarchy.get().getResourceSegmentationReader();

            final Set<QuotaSpec> filteredQuotaSpecs = quotaSpecs.orElseGet(() -> Hierarchy.get().getQuotaSpecReader().getAll());

            final Set<QuotaSpec> validSpecs = filteredQuotaSpecs.stream()
                    .filter(spec -> resourceSegmentationReader.getSegmentations(spec.getResource()).containsAll(querySegmentations))
                    .collect(Collectors.toSet());

            if (validSpecs.isEmpty()) {
                return Optional.empty();
            }
            resultQuotaSpecs = validSpecs;
        }

        return Optional.of(new QuotaFilterParamsImpl(resultQuotaSpecs, resultProjects, segments));
    }
}
