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

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

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.yandex.bolts.collection.Tuple2;
import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest;
import ru.yandex.qe.dispenser.domain.QuotaRequestChangeBuilder;
import ru.yandex.qe.dispenser.domain.dao.GenericDao;
import ru.yandex.qe.dispenser.domain.exception.MultiMessageException;
import ru.yandex.qe.dispenser.domain.exception.SingleMessageException;
import ru.yandex.qe.dispenser.domain.i18n.LocalizableString;
import ru.yandex.qe.dispenser.domain.util.Errors;

@ParametersAreNonnullByDefault
public interface QuotaChangeRequestDao extends QuotaChangeRequestReader, GenericDao<QuotaChangeRequest, Long> {
    @NotNull
    default List<QuotaChangeRequest.Change> updateChangesAmount(@NotNull final QuotaChangeRequest request,
                                                                @NotNull final List<? extends QuotaChangeRequest.ChangeAmount> newChanges) {
        final Map<QuotaChangeRequest.ChangeKey, Long> amountByKey = new HashMap<>();
        for (final QuotaChangeRequest.ChangeAmount newChange : newChanges) {
            amountByKey.put(newChange.getKey(), newChange.getAmount());
        }

        final Map<QuotaChangeRequest.ChangeKey, QuotaChangeRequest.Change> changeByKey = new HashMap<>();
        for (final QuotaChangeRequest.Change change : request.getChanges()) {
            changeByKey.put(change.getKey(), change);
        }

        final Set<QuotaChangeRequest.ChangeKey> keysToRemove = Sets.difference(changeByKey.keySet(), amountByKey.keySet());

        final List<QuotaChangeRequest.Change> changesWithReadyOrAllocated = keysToRemove.stream()
                .map(changeByKey::get)
                .filter(QuotaChangeRequest.Change::hasReadyOrAllocatedOrAllocating)
                .collect(Collectors.toList());

        if (!changesWithReadyOrAllocated.isEmpty()) {
            throw SingleMessageException.illegalArgument("cannot.remove.changes.with.ready.or.allocated",
                    StringUtils.join(changesWithReadyOrAllocated, ","));
        }

        final Errors<LocalizableString> incorrectAmountErrors = new Errors<>();

        final List<QuotaChangeRequest.Change> resultChanges = newChanges.stream()
                .map(c -> {
                    final QuotaChangeRequest.Change change = changeByKey.get(c.getKey());
                    final QuotaRequestChangeBuilder builder;
                    if (change != null) {
                        builder = change.copyBuilder();
                    } else {
                        builder = QuotaChangeRequest.Change.newChangeBuilder();
                    }

                    return builder.key(c.getKey())
                            .amount(c.getAmount())
                            .build();
                })
                .peek(c -> {
                    if (c.getAmount() < c.getAmountReady()) {
                        incorrectAmountErrors.add(LocalizableString.of("cannot.update.change.amount.less.than.ready", c.getResource().getName(), c.getResource().getService().getName()));
                    }
                    if (c.getAmount() < c.getAmountAllocated()) {
                        incorrectAmountErrors.add(LocalizableString.of("cannot.update.change.amount.less.than.allocated", c.getResource().getName(), c.getResource().getService().getName()));
                    }
                })
                .collect(Collectors.toList());

        if (incorrectAmountErrors.hasErrors()) {
            throw MultiMessageException.illegalArgument(incorrectAmountErrors);
        }

        return setChanges(request, resultChanges);
    }

    @NotNull
    List<QuotaChangeRequest.Change> setChanges(@NotNull QuotaChangeRequest request, List<QuotaChangeRequest.Change> resultChanges);

    Set<Long> getBigOrderIdsForRequestsInStatuses(final Set<QuotaChangeRequest.Status> statuses);

    void moveToProject(Collection<Long> requestIds, Project project);

    Set<Long> getRequestsIdsByChangeIds(Set<Long> changedChanges);

    boolean hasRequestsInCampaign(long campaignId);

    boolean hasRequestsInCampaignForOrdersOtherThan(final long campaignId, final Set<Long> orderIds);

    boolean update(Collection<QuotaChangeRequest> values);

    @NotNull
    Stream<Pair<QuotaChangeRequest, QuotaChangeRequest>> setStatuses(final QuotaChangeRequestFilter filter,
                                                                     final QuotaChangeRequest.Status status,
                                                                     final long updateTimeMillis);

    @NotNull
    Stream<ReportQuotaChangeRequest> readReport(QuotaChangeRequestFilter filter,
                                                boolean showGoalQuestions,
                                                boolean filterEmptyChange,
                                                boolean showGoalHierarchy);

    void updateChanges(final Collection<QuotaChangeRequest.Change> changesToUpdate);

    void updateChangesOwningCost(final Collection<QuotaChangeRequest.Change> changesToUpdate);
    void updateRequestsOwningCost(Map<Long, Long> requestOwningCostMap);

    void updateUnbalanced(Map<Long, Boolean> unbalancedByRequestId);

    void setReadyForAllocationState(final Collection<Long> requestIds, final boolean isReadyForAllocation);

    Map<Long, QuotaChangeRequest> readForUpdate(final Collection<Long> ids);

    Map<String, QuotaChangeRequest> findByTicketKeysForUpdate(Collection<String> ticketKeys);

    void setChangesReadyAmount(Map<Long, Long> readyByChangeId);

    void setChangesAllocatedAmount(Map<Long, Long> allocatedByChangeId);

    void setChangesAllocatingAmount(Map<Long, Long> allocatingByChangeId);

    void incrementChangesReadyAmount(@NotNull final Map<Long, Long> readyIncrementByChangeId);

    void incrementChangesAllocatedAmount(@NotNull final Map<Long, Long> allocatedIncrementByChangeId);

    void incrementChangesReadyAndAllocatedAmount(@NotNull final Map<Long, Pair<Long, Long>> readyAndAllocatedIncrementByChangeId);

    @NotNull
    List<QuotaChangeInRequest> selectSegmentedChangesForQuotaDistributionUpdate(
            final long campaignId, final long bigOrderId, final long serviceId,
            @NotNull final QuotaChangeRequest.Type type,
            @NotNull final Set<QuotaChangeRequest.Status> statuses,
            @NotNull final Set<ResourceSegments> resourceSegments, final boolean lock);

    @NotNull
    List<QuotaChangeInRequest> selectNonSegmentedChangesForQuotaDistributionUpdate(
            final long campaignId, final long bigOrderId, final long serviceId,
            @NotNull final QuotaChangeRequest.Type type,
            @NotNull final Set<QuotaChangeRequest.Status> statuses,
            @NotNull final Set<Long> resourceIds, final boolean lock);

    @NotNull
    Map<Long, List<QuotaChangeRequest.Change>> selectChangesByRequestIds(@NotNull final List<Long> requestIds);

    @NotNull
    Map<Long, Pair<QuotaChangeRequest.Status, String>> selectStatusAndIssueByRequestIds(@NotNull final List<Long> requestIds);

    void updateRequestCost(final Set<Long> requestIds);

    List<QuotaChangeRequest.Change> readChangesByRequestAndProviderForUpdate(long requestId, long serviceId);

    @NotNull
    List<QuotaChangeRequest> readRequestsByCampaignsForUpdate(Collection<Long> campaignIds, @Nullable Long fromId, long limit);

    List<Long> getIdsFirstPageByCampaigns(Collection<? extends Long> campaignIds, long limit);

    List<Long> getIdsNextPageByCampaigns(Collection<? extends Long> campaignIds, long from, long limit);

    void setShowAllocationNote(Collection<Long> requestIds, boolean showAllocationNote);

    List<Tuple2<Long, Long>> readRequestOwningCostByCampaignId(Long fromId, Long campaignId, long limit, Set<QuotaChangeRequest.Status> validStatuses);
}
