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

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;

import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;

import ru.yandex.qe.dispenser.api.v1.DiQuotaChangeRequestImportantFilter;
import ru.yandex.qe.dispenser.api.v1.DiQuotaChangeRequestSortField;
import ru.yandex.qe.dispenser.api.v1.DiQuotaChangeRequestUnbalancedFilter;
import ru.yandex.qe.dispenser.api.v1.DiResourcePreorderReasonType;
import ru.yandex.qe.dispenser.domain.Campaign;
import ru.yandex.qe.dispenser.domain.ClosestResponsibleHelper;
import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.dao.person.PersonReader;
import ru.yandex.qe.dispenser.domain.dao.project.ProjectReader;
import ru.yandex.qe.dispenser.domain.dao.quota.request.QuotaChangeRequestReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.util.StreamUtils;

@Component
public class QuotaChangeRequestFilterParamConverter {
    private final ClosestResponsibleHelper closestResponsibleHelper;

    @Inject
    public QuotaChangeRequestFilterParamConverter(final ClosestResponsibleHelper closestResponsibleHelper) {
        this.closestResponsibleHelper = closestResponsibleHelper;
    }

    public Optional<QuotaChangeRequestReader.QuotaChangeRequestFilter> fromParam(final QuotaChangeRequestFilterParam param) {
        return getFilteredProjects(param).map(projects -> new Filter(param, projects));
    }

    @NotNull
    private Optional<Set<Project>> getProjects(final QuotaChangeRequestFilterParam param) {
        final ProjectReader reader = Hierarchy.get().getProjectReader();

        final Stream<Project> projectStream;

        final Set<Integer> abcServiceIds = param.getAbcServiceIds();
        final Set<Integer> ancestorAbcServiceId = param.getAncestorAbcServiceId();
        final Set<String> projectKeys = param.getProjectKeys();

        final boolean isServicesInFilter = CollectionUtils.isNotEmpty(abcServiceIds);
        final boolean isAncestorServiceInFilter = ancestorAbcServiceId != null && !ancestorAbcServiceId.isEmpty();

        if (isServicesInFilter || isAncestorServiceInFilter) {

            final Set<Stream<Project>> streams = new HashSet<>();

            if (isServicesInFilter) {
                streams.add(reader.getAll().stream()
                        .filter(p -> abcServiceIds.contains(p.getAbcServiceId()))
                );
            }
            if (isAncestorServiceInFilter) {
                streams.add(reader.getAll().stream()
                        .filter(p -> ancestorAbcServiceId.contains(p.getAbcServiceId()))
                        .flatMap(p -> StreamUtils.getAllDescendants(p.getSubProjects(), Project::getSubProjects))
                );
            }

            projectStream = streams.stream().flatMap(Function.identity());

        } else if (CollectionUtils.isNotEmpty(projectKeys)) {

            projectStream = projectKeys.stream()
                    .map(reader::readExisting)
                    .flatMap(project -> project.getExistingSubProjects().stream());

        } else {
            return Optional.empty();
        }

        return Optional.of(projectStream.collect(Collectors.toSet()));
    }

    private Optional<Set<Project>> getProjectOfResponsible(final Set<String> responsible) {
        if (CollectionUtils.isEmpty(responsible)) {
            return Optional.empty();
        }

        final PersonReader reader = Hierarchy.get().getPersonReader();

        return Optional.of(responsible
                .stream()
                .flatMap(login -> Hierarchy.get().getResponsibleProjects(reader.read(login)).stream()
                        .filter(p -> closestResponsibleHelper.getClosestResponsible(p).map(login::equals).orElse(false))
                ).collect(Collectors.toSet())
        );
    }

    private Optional<Set<Project>> getAllProjects(final QuotaChangeRequestFilterParam param) {
        final Optional<Set<Project>> projects = getProjects(param);
        final Optional<Set<Project>> projectOfResponsible = getProjectOfResponsible(param.getResponsible());

        if (projectOfResponsible.isPresent() && projects.isPresent()) {
            return Optional.of(Sets.intersection(projectOfResponsible.get(), projects.get()));
        }

        if (projectOfResponsible.isPresent()) {
            return projectOfResponsible;
        }

        return projects;
    }

    private Optional<Set<Project>> getFilteredProjects(final QuotaChangeRequestFilterParam param) {
        final Optional<Set<Project>> allProjects = getAllProjects(param);

        if (!allProjects.isPresent()) {
            return Optional.of(Collections.emptySet());
        }

        return allProjects.filter(s -> !s.isEmpty());
    }

    private static class Filter implements QuotaChangeRequestReader.QuotaChangeRequestFilter {

        private final QuotaChangeRequestFilterParam param;
        private final Set<Project> projects;

        private Filter(final QuotaChangeRequestFilterParam param, final Set<Project> projects) {
            this.param = param;
            this.projects = projects;
        }

        @Override
        @NotNull
        public Set<Person> getAuthors() {
            final PersonReader reader = Hierarchy.get().getPersonReader();

            return Optional.ofNullable(param.getAuthors())
                    .orElse(Collections.emptySet())
                    .stream()
                    .map(reader::read)
                    .collect(Collectors.toSet());
        }

        @Override
        @NotNull
        public Set<Project> getProjects() {
            return projects;
        }

        @Override
        @NotNull
        public Set<QuotaChangeRequest.Status> getStatus() {
            return Optional.ofNullable(param.getStatus())
                    .map(statuses -> statuses.stream()
                            .map(QuotaChangeRequest.Status::of)
                            .collect(Collectors.toSet()))
                    .orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public Set<QuotaChangeRequest.Type> getType() {
            return Optional.ofNullable(param.getType())
                    .map(statuses -> statuses.stream()
                            .map(QuotaChangeRequest.Type::of)
                            .collect(Collectors.toSet()))
                    .orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public Set<Service> getServices() {
            return param.getServices();
        }

        @Override
        @NotNull
        public Set<Long> getOrderIds() {
            return Optional.ofNullable(param.getOrderIds()).orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public Set<Long> getCampaignIds() {
            final Set<Long> campaignIds = param.getCampaignIds();
            return campaignIds != null ? campaignIds : Collections.emptySet();
        }

        @Override
        @NotNull
        public Set<Long> getCampaignOrderIds() {
            final Set<Long> campaignOrderIds = param.getCampaignOrderIds();
            return campaignOrderIds != null ? campaignOrderIds : Collections.emptySet();
        }

        @Override
        @NotNull
        public Set<Long> getGoalIds() {
            return Optional.ofNullable(param.getGoalsIds()).orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public Set<DiResourcePreorderReasonType> getReasonTypes() {
            return Optional.ofNullable(param.getReasonTypes()).orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public Set<Long> getExcludedChangeRequestIds() {
            return Collections.emptySet();
        }

        @Override
        @NotNull
        public Optional<Boolean> withoutTicket() {
            return Optional.empty();
        }

        @Override
        @NotNull
        public Set<Long> getChangeRequestIds() {
            return Collections.emptySet();
        }

        @Override
        @NotNull
        public Set<Long> getPreOrderIds() {
            return Optional.ofNullable(param.getPreOrderIds()).orElse(Collections.emptySet());
        }

        @Override
        @NotNull
        public QuotaChangeRequestReader.SortOrder getSortOrder() {
            final QuotaChangeRequestReader.SortOrder sortOrder = param.getSortOrder();
            return sortOrder == null ? QuotaChangeRequestReader.SortOrder.DESC : sortOrder;
        }

        @Override
        public @NotNull DiQuotaChangeRequestSortField getSortBy() {
            final DiQuotaChangeRequestSortField sortBy = param.getSortBy();
            return sortBy == null ? DiQuotaChangeRequestSortField.UPDATED_AT : sortBy;
        }

        @Override
        @NotNull
        public Optional<String> getSummary() {
            return Optional.ofNullable(param.getSummary());
        }

        @Override
        @Nullable
        public Long getOwningCostGreaterOrEquals() {
            return param.getOwningCostGreaterOrEquals();
        }

        @Override
        @Nullable
        public Long getOwningCostLessOrEquals() {
            return param.getOwningCostLessOrEquals();
        }

        @Override
        @Nullable
        public DiQuotaChangeRequestImportantFilter getImportantFilter() {
            return param.getImportantFilter();
        }

        @Override
        @Nullable
        public DiQuotaChangeRequestUnbalancedFilter getUnbalancedFilter() {
            return param.getUnbalancedFilter();
        }

        @Override
        @NotNull
        public Set<Campaign.Type> getCampaignTypes() {
            return Optional.ofNullable(param.getCampaignTypes()).orElse(Collections.emptySet());
        }
    }
}
