package ru.yandex.qe.dispenser.ws;

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

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;

import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ru.yandex.qe.dispenser.domain.MessageHelper;
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest;
import ru.yandex.qe.dispenser.domain.Resource;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.dao.quota.request.QuotaChangeRequestDao;
import ru.yandex.qe.dispenser.domain.dao.quota.request.QuotaChangeRequestFilterImpl;
import ru.yandex.qe.dispenser.domain.index.LongIndexBase;
import ru.yandex.qe.dispenser.quartz.trigger.QuartzTrackerComment;
import ru.yandex.qe.dispenser.ws.quota.request.QuotaChangeRequestManager;
import ru.yandex.qe.dispenser.ws.quota.request.ticket.QuotaChangeRequestTicketManager;
import ru.yandex.qe.dispenser.ws.quota.request.workflow.context.PerformerContext;

@Component
@ParametersAreNonnullByDefault
public class ResourcePreorderChangeManager {

    private final static Function<QuotaChangeRequest.Change, Long> AMOUNT_READY_SUPPLIER = QuotaChangeRequest.Change::getAmountReady;
    private final static Function<QuotaChangeRequest.Change, Long> AMOUNT_ALLOCATED_SUPPLIER = QuotaChangeRequest.Change::getAmountAllocated;

    private static final Multimap<String, String> IGNORED_ALLOCATION_RESOURCE_KEYS_BY_PROVIDER_KEY = ImmutableMultimap.<String, String>builder()
            .put("nirvana", "ram")
            .put("sandbox", "ram")
            .build();

    private static final Multimap<String, String> RESOURCE_WITHOUT_MANUAL_ALLOCATION_KEYS_BY_PROVIDER_KEY = ImmutableMultimap.<String, String>builder()
            .build();

    private final QuotaChangeRequestManager requestManager;
    private final QuotaChangeRequestDao quotaChangeRequestDao;
    private final MessageHelper messageHelper;
    private final QuartzTrackerComment quartzTrackerCommentTrigger;

    @Inject
    public ResourcePreorderChangeManager(final QuotaChangeRequestManager requestManager,
                                         final QuotaChangeRequestDao quotaChangeRequestDao,
                                         final MessageHelper messageHelper,
                                         final QuartzTrackerComment quartzTrackerCommentTrigger) {
        this.requestManager = requestManager;
        this.quotaChangeRequestDao = quotaChangeRequestDao;
        this.messageHelper = messageHelper;
        this.quartzTrackerCommentTrigger = quartzTrackerCommentTrigger;
    }

    public void updateRequestChanges(final PerformerContext context,
                                     final QuotaRequestChangeValues changeValues,
                                     final boolean suppressSummon) {

        requestManager.setChangesReadyAmount(changeValues.getAffectedInReadyAmountUpdateRequests(), changeValues.getReadyByChangeId(), context);

        requestManager.setChangesAllocatedAmount(changeValues.getAffectedInAllocatedAmountUpdateRequests(), changeValues.getAllocatedByChangeId(), context);

        requestManager.setChangesAllocatingAmount(changeValues.getAffectedInAllocatingAmountUpdateRequests(), changeValues.getAllocatingByChangeId(), context);

        requestManager.setReadyForAllocationState(changeValues.getRequestIdsToResetReadyAllocationState(), false, context);

        final Collection<QuotaChangeRequest> requests = quotaChangeRequestDao.read(changeValues.getRequestIdsWithUpdate()).values();

        final Set<Long> completedRequestIds = requests.stream()
                .filter(this::isCompleted)
                .map(LongIndexBase::getId)
                .collect(Collectors.toSet());

        if (!completedRequestIds.isEmpty()) {
            final QuotaChangeRequestFilterImpl filter = new QuotaChangeRequestFilterImpl();
            filter.setChangeRequestIds(completedRequestIds);
            filter.setStatuses(Set.of(QuotaChangeRequest.Status.NEW, QuotaChangeRequest.Status.READY_FOR_REVIEW,
                    QuotaChangeRequest.Status.NEED_INFO, QuotaChangeRequest.Status.APPROVED,
                    QuotaChangeRequest.Status.CONFIRMED));

            requestManager.setStatusBatch(filter, QuotaChangeRequest.Status.COMPLETED, context, suppressSummon);
        }

        postAmountCommentsToTracker(changeValues, context);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void postAmountCommentsToTracker(final QuotaRequestChangeValues changeValues, final PerformerContext context) {
        final Set<QuotaChangeRequest> affectedRequests = Sets.union(
                changeValues.getAffectedInAllocatedAmountUpdateRequests(),
                changeValues.getAffectedInReadyAmountUpdateRequests()
        );

        for (final QuotaChangeRequest quotaChangeRequest : affectedRequests) {
            if (quotaChangeRequest.getTrackerIssueKey() == null) {
                continue;
            }

            final List<QuotaChangeRequest.Change> changesReady = new ArrayList<>();
            final List<QuotaChangeRequest.Change> changesAllocated = new ArrayList<>();
            final List<QuotaChangeRequest.Change> diffAllocated = new ArrayList<>();

            for (final QuotaChangeRequest.Change change : quotaChangeRequest.getChanges()) {
                fillAmount(changeValues, changesReady, changesAllocated, diffAllocated, change);
            }


            if (changesReady.isEmpty() && changesAllocated.isEmpty()) {
                continue;
            }

            final String commentForRequest = getCommentForRequest(quotaChangeRequest.getStatus(), changesReady, changesAllocated,
                    diffAllocated, changeValues.getCommentByRequestId().get(quotaChangeRequest.getId()), context);
            quartzTrackerCommentTrigger.run(quotaChangeRequest.getTrackerIssueKey(), commentForRequest);
        }
    }

    public String getCommentForRequest(final QuotaChangeRequest.Status requetStatus, final List<QuotaChangeRequest.Change> changesReady,
                                       final List<QuotaChangeRequest.Change> changesAllocated,
                                       final List<QuotaChangeRequest.Change> changesAllocatedDiff,
                                       @Nullable final String comment,
                                       final PerformerContext context) {
        final StringBuilder sb = new StringBuilder();
        if (!changesReady.isEmpty()) {
            sb.append(getFormattedReadyComment(requetStatus, changesReady, context));
        }

        if (!changesReady.isEmpty() && !changesAllocated.isEmpty()) {
            sb.append("----\n");
        }

        if (!changesAllocatedDiff.isEmpty()) {
            sb.append(getFormattedAllocatedComment(changesAllocatedDiff, context));
            sb.append("----\n");
        }

        if (!changesAllocated.isEmpty()) {
            sb.append(getFormattedAllocatedAllComment(changesAllocated));
            sb.append("----\n");
        }

        if (StringUtils.isNotEmpty(comment)) {
            sb.append("\nКомментарий: ")
                    .append(comment);
        }

        return sb.toString();
    }

    private String getCommentByChanges(final List<QuotaChangeRequest.Change> changes,
                                       final Function<QuotaChangeRequest.Change, Long> amountSupplier) {
        boolean singleProvider = isSingleProvider(changes);
        String providerPrefix = providerPrefix(singleProvider, changes);
        final List<List<String>> resources = QuotaChangeRequestTicketManager.formatChanges(changes, amountSupplier,
                !singleProvider);
        return providerPrefix + QuotaChangeRequestTicketManager.toWikiTableWithBoldResourceSummary(resources);
    }

    private String providerPrefix(boolean singleProvider, List<QuotaChangeRequest.Change> changes) {
        if (!singleProvider) {
            return "";
        }
        String providerName = changes.get(0).getResource().getService().getName();
        return messageHelper.format("quota.request.preorder.ticket.provider.comment", providerName) + "\n";
    }

    private boolean isSingleProvider(List<QuotaChangeRequest.Change> changes) {
        return changes.stream().map(c -> c.getResource().getService().getId()).collect(Collectors.toSet()).size() == 1;
    }

    private String getFormattedReadyComment(final QuotaChangeRequest.Status requestStatus, final List<QuotaChangeRequest.Change> changes, final PerformerContext context) {
        final boolean manualQuotaAllocation = changes.iterator().next().getResource().getService().getSettings().isManualQuotaAllocation();
        return messageHelper.format("quota.request.preorder.ticket.amount.change.ready.comment",
                context.getPerson(),
                getCommentByChanges(changes, AMOUNT_READY_SUPPLIER),
                manualQuotaAllocation ? 1 : 0,
                manualQuotaAllocation && requestStatus != QuotaChangeRequest.Status.CONFIRMED ? 1 : 0,
                getStatusTitle(requestStatus)
        );
    }

    private String getStatusTitle(QuotaChangeRequest.Status status) {
        return messageHelper.format("quota.request.ticket.status." + status.name() + ".title");
    }

    private String getFormattedAllocatedComment(final List<QuotaChangeRequest.Change> changes, final PerformerContext context) {
        return messageHelper.format("quota.request.preorder.ticket.amount.change.allocated.comment",
                context.getPerson(),
                getCommentByChanges(changes, AMOUNT_ALLOCATED_SUPPLIER)
        );
    }

    private String getFormattedAllocatedAllComment(final List<QuotaChangeRequest.Change> changes) {
        return messageHelper.format("quota.request.preorder.ticket.amount.change.allocated.all.comment",
                getCommentByChanges(changes, AMOUNT_ALLOCATED_SUPPLIER)
        );
    }

    private static void fillAmount(final QuotaRequestChangeValues changeValues, final List<QuotaChangeRequest.Change> changesReady,
                                   final List<QuotaChangeRequest.Change> changesAllocated,
                                   final List<QuotaChangeRequest.Change> diffChangesAllocated,
                                   final QuotaChangeRequest.Change change) {

        final Long amountAllocated = changeValues.getAllocatedByChangeId().get(change.getId());
        final Long amountReady = changeValues.getReadyByChangeId().get(change.getId());

        if (amountAllocated != null && amountReady != null) {
            if (!amountAllocated.equals(amountReady)) {
                changesReady.add(change.copyBuilder().amountReady(amountReady).build());
            }
            changesAllocated.add(change.copyBuilder().amountAllocated(amountAllocated)
                    .amountAllocating(amountAllocated).build());
            diffChangesAllocated.add(change.copyBuilder().amountAllocated(amountAllocated - change.getAmountAllocated())
                    .amountAllocating(amountAllocated).build());
        } else if (amountReady != null) {
            changesReady.add(change.copyBuilder().amountReady(amountReady).build());
        } else if (amountAllocated != null) {
            changesAllocated.add(change.copyBuilder().amountAllocated(amountAllocated)
                    .amountAllocating(amountAllocated).build());
            diffChangesAllocated.add(change.copyBuilder().amountAllocated(amountAllocated - change.getAmountAllocated())
                    .amountAllocating(amountAllocated).build());
        }
    }


    private boolean isCompleted(final QuotaChangeRequest request) {
        return request.getChanges().stream()
                .filter(c -> !IGNORED_ALLOCATION_RESOURCE_KEYS_BY_PROVIDER_KEY.get(c.getResource().getService().getKey()).contains(c.getResource().getPublicKey()))
                .allMatch(c -> c.getAmountReady() == c.getAmount() && c.getAmountAllocated() == c.getAmount());
    }

    public boolean isResourceRequiredForCompletion(@NotNull final Service service,
                                                   @NotNull final Resource resource) {
        final Collection<String> ignoredKeys = IGNORED_ALLOCATION_RESOURCE_KEYS_BY_PROVIDER_KEY.get(service.getKey());
        return !ignoredKeys.contains(resource.getPublicKey());
    }

    public boolean isResourceCanBeAllocated(@NotNull final QuotaChangeRequest.Change change) {
        final Resource resource = change.getResource();
        final Service service = resource.getService();
        final Collection<String> ignoredKeys = RESOURCE_WITHOUT_MANUAL_ALLOCATION_KEYS_BY_PROVIDER_KEY.get(service.getKey());

        return change.getAmountReady() > change.getAmountAllocated()
                && change.getAmountAllocated() == change.getAmountAllocating()
                && !ignoredKeys.contains(resource.getPublicKey())
                && isResourceRequiredForCompletion(service, resource);
    }

}
