package ru.yandex.qe.dispenser.ws;

import java.math.BigInteger;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;

import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ru.yandex.qe.dispenser.api.v1.DiAmount;
import ru.yandex.qe.dispenser.api.v1.DiBotBigOrder;
import ru.yandex.qe.dispenser.api.v1.DiQuotaChangeRequest;
import ru.yandex.qe.dispenser.api.v1.ExpandQuotaChangeRequest;
import ru.yandex.qe.dispenser.api.v1.response.DiListResponse;
import ru.yandex.qe.dispenser.domain.CampaignOwningCost;
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.Resource;
import ru.yandex.qe.dispenser.domain.Segment;
import ru.yandex.qe.dispenser.domain.base_resources.BaseResourceChange;
import ru.yandex.qe.dispenser.domain.bot.BotUtils;
import ru.yandex.qe.dispenser.domain.dao.bot.preorder.change.BotPreOrderChangeDao;
import ru.yandex.qe.dispenser.domain.dao.campaign.CampaignOwningCostCache;
import ru.yandex.qe.dispenser.domain.dao.project.ProjectReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.hierarchy.HierarchySupplier;
import ru.yandex.qe.dispenser.domain.hierarchy.Role;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.domain.index.LongIndexBase;
import ru.yandex.qe.dispenser.domain.util.LocalizationUtils;
import ru.yandex.qe.dispenser.ws.allocation.ProviderAllocationInfo;
import ru.yandex.qe.dispenser.ws.base_resources.impl.BaseResourcesMapper;
import ru.yandex.qe.dispenser.ws.base_resources.impl.QuotaChangeRequestBaseResources;
import ru.yandex.qe.dispenser.ws.bot.Provider;
import ru.yandex.qe.dispenser.ws.quota.request.ChangeComparator;
import ru.yandex.qe.dispenser.ws.quota.request.owning_cost.pricing.PricingModel;
import ru.yandex.qe.dispenser.ws.quota.request.owning_cost.pricing.QuotaChangeOwningCostTariffManager;
import ru.yandex.qe.dispenser.ws.quota.request.ticket.QuotaChangeRequestTicketManager;
import ru.yandex.qe.dispenser.ws.quota.request.workflow.QuotaRequestWorkflowManager;
import ru.yandex.qe.dispenser.ws.quota.request.workflow.ResourceWorkflow;
import ru.yandex.qe.dispenser.ws.quota.request.workflow.context.RequestContext;

import static ru.yandex.qe.dispenser.ws.quota.request.owning_cost.campaignOwningCost.CampaignOwningCostRefreshTransactionWrapper.VALID_STATUSES;
import static ru.yandex.qe.dispenser.ws.quota.request.owning_cost.formula.ProviderOwningCostFormula.percentageOfCampaignOwningCostOutputString;
import static ru.yandex.qe.dispenser.ws.quota.request.owning_cost.formula.ProviderOwningCostFormula.percentageOfRequestOwningCostOutputString;
import static ru.yandex.qe.dispenser.ws.quota.request.owning_cost.formula.ProviderOwningCostFormula.relativeCostToProviderQuotaPrice;

@ParametersAreNonnullByDefault
@Component
public class QuotaRequestSerializer {
    private static final String PROVIDER_QUOTA_PRICE_SKU = "yp.cpu.quota";
    private static final Logger LOG = LoggerFactory.getLogger(QuotaRequestSerializer.class);
    @NotNull
    private final QuotaRequestWorkflowManager quotaRequestWorkflowManager;
    @NotNull
    private final QuotaChangeOwningCostTariffManager quotaChangeOwningCostTariffManager;
    @NotNull
    private final BotPreOrderChangeDao botPreOrderChangeDao;
    @NotNull
    private final BotUtils botUtils;
    @NotNull
    private final ResourceRequestAllocationManager requestAllocationManager;
    private final BaseResourcesMapper baseResourcesMapper;
    private final HierarchySupplier hierarchySupplier;
    private final MessageSource errorMessageSource;
    private final CampaignOwningCostCache campaignOwningCostCache;


    @Inject
    public QuotaRequestSerializer(final QuotaRequestWorkflowManager quotaRequestWorkflowManager,
                                  final QuotaChangeOwningCostTariffManager quotaChangeOwningCostTariffManager,
                                  final BotPreOrderChangeDao botPreOrderChangeDao,
                                  final BotUtils botUtils,
                                  final ResourceRequestAllocationManager requestAllocationManager,
                                  final CampaignOwningCostCache campaignOwningCostCache,
                                  BaseResourcesMapper baseResourcesMapper,
                                  HierarchySupplier hierarchySupplier,
                                  @Qualifier("errorMessageSource") MessageSource errorMessageSource) {
        this.quotaRequestWorkflowManager = quotaRequestWorkflowManager;
        this.quotaChangeOwningCostTariffManager = quotaChangeOwningCostTariffManager;
        this.botPreOrderChangeDao = botPreOrderChangeDao;
        this.botUtils = botUtils;
        this.requestAllocationManager = requestAllocationManager;
        this.baseResourcesMapper = baseResourcesMapper;
        this.hierarchySupplier = hierarchySupplier;
        this.errorMessageSource = errorMessageSource;
        this.campaignOwningCostCache = campaignOwningCostCache;
    }

    @SuppressWarnings("OverlyCoupledMethod")
    @NotNull
    private DiQuotaChangeRequest toView(final QuotaChangeRequest request, final Context context,
                                        Set<ExpandQuotaChangeRequest> expand,
                                        Set<BaseResourceChange> baseChanges) {
        final long requestId = request.getId();
        final QuotaChangeRequest.Campaign campaign = request.getCampaign();

        final Project project = request.getProject();

        final QuotaChangeRequest.Type type = request.getType();
        final boolean isResourcePreorder = type == QuotaChangeRequest.Type.RESOURCE_PREORDER;
        final Collection<Long> botPreOrderIds = context.preOrderIdsByRequestId.get(requestId);
        final String botPreOrdersUrl = getBotPreOrdersUrl(botPreOrderIds, type);
        final String responsible = QuotaChangeRequestTicketManager.getAssignee(request, Hierarchy.get());
        final PricingModel providerQuotaPrice = quotaChangeOwningCostTariffManager
                .getByProviderCampaign(Provider.YP, request.getCampaign() != null ? request.getCampaign().getKey() : null).stream()
                .filter(p -> p.getSKU().equals(PROVIDER_QUOTA_PRICE_SKU))
                .findFirst()
                .orElse(null);
        if (providerQuotaPrice == null) {
            LOG.error(String.format("Missing \"%s\" provider quota price with sku \"%s\"", Provider.YP, PROVIDER_QUOTA_PRICE_SKU));
        }
        Map<Long, BigInteger> campaignOwningCostByCampaignId = campaignOwningCostCache.getAll().stream()
                .collect(Collectors.toMap(CampaignOwningCost::getCampaignId, CampaignOwningCost::getOwningCost));

        Set<String> deliverableProvidersKeys = requestAllocationManager.getDeliverableProviders(request);
        DiQuotaChangeRequest.Builder builder = new DiQuotaChangeRequest.Builder(requestId)
                .project(project.toView(false))
                .changes(request.getChanges().stream()
                        .sorted(ChangeComparator.INSTANCE)
                        .map(c -> {
                            final Resource resource = c.getResource();
                            return new DiQuotaChangeRequest.Change(c.getKey().getBigOrder() == null ? null : c.getKey().getBigOrder().toView(),
                                    new DiQuotaChangeRequest.Service(resource.getService().getKey(), resource.getService().getName()),
                                    new DiQuotaChangeRequest.Resource(resource.getPublicKey(), resource.getName()),
                                    c.getSegments().stream().map(Segment::getPublicKey).collect(Collectors.toSet()),
                                    DiAmount.of(c.getAmount(), resource.getType().getBaseUnit()),
                                    DiAmount.of(c.getAmountReady(), resource.getType().getBaseUnit()),
                                    DiAmount.of(c.getAmountAllocated(), resource.getType().getBaseUnit()),
                                    DiAmount.of(c.getAmountAllocating(), resource.getType().getBaseUnit()),
                                    isResourcePreorder && ResourceWorkflow.canUserViewMoney(context.getPerformer())
                                            ? c.getOwningCost().setScale(0, RoundingMode.HALF_UP).toString()
                                            : null,
                                    percentageOfRequestOwningCostOutputString(c.getOwningCost(), request.getRequestOwningCost())
                            );
                        })
                        .collect(Collectors.toList()))
                .author(request.getAuthor().getLogin())
                .description(request.getDescription())
                .comment(request.getComment())
                .calculations(request.getCalculations())
                .created(request.getCreated())
                .updated(request.getUpdated())
                .status(request.getStatus().toView())
                .type(type.toView())
                .trackerIssueKey(request.getTrackerIssueKey())
                .sourceProject(request.getSourceProject() == null ? null : request.getSourceProject().toView())
                .permissions(new HashSet<>(context.permissions.get(request)))
                .chartLinks(request.getChartLinks())
                .chartLinksAbsenceExplanation(request.getChartLinksAbsenceExplanation())
                .botPreOrderIds(authorizeBotPreOrderIds(botPreOrderIds, type))
                .additionalProperties(request.getAdditionalProperties())
                .campaign(campaign == null ? null : campaign.toView())
                .resourcePreorderReasonType(request.getResourcePreorderReasonType())
                .goal(request.getGoal() == null ? null : request.getGoal().toView())
                .readyForAllocation(request.isReadyForAllocation())
                .requestGoalAnswers(isResourcePreorder ? request.getRequestGoalAnswers() : new HashMap<>())
                .cost(isResourcePreorder
                        && ResourceWorkflow.canUserViewBotMoney(context.getPerformer()) ? request.getCost() : null)
                .requestOwningCost(isResourcePreorder
                        && ResourceWorkflow.canUserViewMoney(context.getPerformer()) ? String.valueOf(request.getRequestOwningCost()) : null)
                .relativeCost(isResourcePreorder
                        && providerQuotaPrice != null
                        ? relativeCostToProviderQuotaPrice(request.getRequestOwningCost(), providerQuotaPrice.getPrice())
                        : null)
                .percentageOfCampaignOwningCost(isResourcePreorder && campaign != null
                        && campaignOwningCostByCampaignId.containsKey(campaign.getId())
                        && VALID_STATUSES.contains(request.getStatus())
                        ? percentageOfCampaignOwningCostOutputString(request.getRequestOwningCost(), campaignOwningCostByCampaignId.get(campaign.getId()))
                        : null)
                .botPreOrdersUrl(botPreOrdersUrl)
                .summary(request.getSummary())
                .responsible(responsible)
                .showAllocationNote(request.showAllocationNote())
                .providersToAllocate(context.getProvidersToAllocate(request, deliverableProvidersKeys))
                .importantRequest(request.isImportantRequest())
                .unbalanced(request.isUnbalanced());
        if (expand != null) {
            if (expand.contains(ExpandQuotaChangeRequest.BASE_RESOURCES)) {
                builder.addBaseResourceChanges(toBaseResourceChanges(baseResourcesMapper
                        .expandBaseResourceChanges(baseChanges)));
            }
            if (expand.contains(ExpandQuotaChangeRequest.EXTRA_REPORT_FIELDS)) {
                builder.extraReportFields(toExtraReportFields(request));
            }
        }
        return builder.build();
    }

    @NotNull
    @Transactional(propagation = Propagation.REQUIRED)
    public DiQuotaChangeRequest toView(final QuotaChangeRequest quotaChangeRequest, final Person performer,
                                       Set<ExpandQuotaChangeRequest> expand,
                                       Set<BaseResourceChange> baseChanges) {
        return toView(quotaChangeRequest, new Context(quotaChangeRequest, performer), expand, baseChanges);
    }


    @NotNull
    @Transactional(propagation = Propagation.REQUIRED)
    public DiListResponse<DiQuotaChangeRequest> toView(final Collection<QuotaChangeRequest> quotaChangeRequests,
                                                       final Person performer, Set<ExpandQuotaChangeRequest> expand,
                                                       Map<Long, Set<BaseResourceChange>> baseChangesByQuotaRequest) {
        final Context context = new Context(quotaChangeRequests, performer);

        return new DiListResponse<>(quotaChangeRequests.stream()
                .map(request -> toView(request, context, expand,
                        baseChangesByQuotaRequest.getOrDefault(request.getId(), Set.of())))
                .collect(Collectors.toList()));
    }

    @NotNull
    @Transactional(propagation = Propagation.REQUIRED)
    public Collection<DiQuotaChangeRequest> toViewCollection(final Collection<QuotaChangeRequest> quotaChangeRequests,
                                                             final Person performer,
                                                             Set<ExpandQuotaChangeRequest> expand,
                                                             Map<Long, Set<BaseResourceChange>> baseChangesByQuotaRequest) {
        final Context context = new Context(quotaChangeRequests, performer);

        return quotaChangeRequests.stream()
                .map(request -> toView(request, context, expand,
                        baseChangesByQuotaRequest.getOrDefault(request.getId(), Set.of())))
                .collect(Collectors.toList());
    }

    @NotNull
    private Collection<Long> authorizeBotPreOrderIds(final Collection<Long> botPreOrderIds,
                                                     final QuotaChangeRequest.Type type) {
        if (type != QuotaChangeRequest.Type.RESOURCE_PREORDER) {
            return Collections.emptyList();
        }
        if (botPreOrderIds.isEmpty()) {
            return botPreOrderIds;
        }
        return botPreOrderIds;
    }

    @Nullable
    private String getBotPreOrdersUrl(final Collection<Long> botPreOrderIds, final QuotaChangeRequest.Type type) {
        if (type != QuotaChangeRequest.Type.RESOURCE_PREORDER) {
            return null;
        }
        if (botPreOrderIds.isEmpty()) {
            return null;
        }
        return botUtils.getBotPreOrdersUrl(botPreOrderIds);
    }

    private Set<DiQuotaChangeRequest.BaseResourceChange> toBaseResourceChanges(
            QuotaChangeRequestBaseResources values) {
        Set<DiQuotaChangeRequest.BaseResourceChange> result = new HashSet<>();
        values.getAmounts().forEach(amount -> {
            Set<String> segmentKeys = amount.getBaseResourceSegments().stream().map(Segment::getPublicKey)
                    .collect(Collectors.toSet());
            DiQuotaChangeRequest.BaseResourceType type = new DiQuotaChangeRequest.BaseResourceType(
                    amount.getBaseResourceType().getId(), amount.getBaseResourceType().getName(),
                    amount.getBaseResourceType().getKey(), new DiQuotaChangeRequest.Service(amount.getService()
                    .getKey(), amount.getService().getName()));
            DiQuotaChangeRequest.BaseResourceChange change = new DiQuotaChangeRequest.BaseResourceChange(
                    new DiBotBigOrder(amount.getBigOrder().getId(), amount.getBigOrder().getDate().format(DateTimeFormatter.ISO_LOCAL_DATE)),
                    new DiQuotaChangeRequest.BaseResource(amount.getBaseResource().getId(),
                            amount.getBaseResource().getName(), amount.getBaseResource().getKey(), segmentKeys, type),
                    DiAmount.of(amount.getAmount(), amount.getBaseResourceType().getResourceType().getBaseUnit()),
                    amount.getPerServiceAmounts().stream().map(a ->
                                    new DiQuotaChangeRequest.PerProviderBaseResourceAmount(new DiQuotaChangeRequest.Service(
                                            a.getService().getKey(), a.getService().getName()), DiAmount.of(a.getAmount(),
                                            amount.getBaseResourceType().getResourceType().getBaseUnit()), a.getMappingIds()))
                            .collect(Collectors.toSet()));
            result.add(change);
        });
        return result;
    }

    private DiQuotaChangeRequest.ExtraReportFields toExtraReportFields(QuotaChangeRequest request) {
        ProjectReader projectReader = hierarchySupplier.get().getProjectReader();
        Project requestProject = request.getProject();
        List<Project> fullPathFromRoot = requestProject.getPathFromRoot();
        List<Project> pathFromRootWithoutRoot = fullPathFromRoot
                .subList(Math.min(1, fullPathFromRoot.size()), fullPathFromRoot.size());
        List<Project> pathFromRootSubset;
        if (pathFromRootWithoutRoot.size() >= 3) {
            Project lastParent = pathFromRootWithoutRoot.get(pathFromRootWithoutRoot.size() - 2);
            pathFromRootSubset = pathFromRootWithoutRoot.subList(0, Math.min(pathFromRootWithoutRoot.size() - 2, 2));
            pathFromRootSubset.add(lastParent);
        } else {
            pathFromRootSubset = pathFromRootWithoutRoot;
        }
        List<Set<Person>> heads = new ArrayList<>();
        List<Project> headDepartments = new ArrayList<>();
        for (Project project : pathFromRootSubset) {
            Set<Person> projectResponsibleSet = projectReader.getLinkedResponsibles(project);
            heads.add(projectResponsibleSet);
            headDepartments.add(project);
        }
        DiQuotaChangeRequest.ExtraReportFields.Builder builder = DiQuotaChangeRequest.ExtraReportFields.builder();
        if (pathFromRootSubset.size() > 0) {
            heads.get(0).forEach(h -> builder.addHeadFirst(h.getLogin()));
            builder.headDepartmentFirst(headDepartments.get(0).toView(false));
        }
        if (pathFromRootSubset.size() > 1) {
            heads.get(1).forEach(h -> builder.addHeadSecond(h.getLogin()));
            builder.headDepartmentSecond(headDepartments.get(1).toView(false));
        }
        if (pathFromRootSubset.size() > 2) {
            heads.get(2).forEach(h -> builder.addHeadThird(h.getLogin()));
            builder.headDepartmentThird(headDepartments.get(2).toView(false));
        }
        Long valueStreamAbcServiceId = requestProject.getValueStreamAbcServiceId();
        if (valueStreamAbcServiceId != null) {
            Project valueStreamProject = Iterables.getFirst(projectReader
                    .tryReadByAbcServiceIds(Collections.singleton(valueStreamAbcServiceId.intValue())), null);
            if (valueStreamProject != null) {
                builder.valueStream(valueStreamProject.toView(false));
                builder.addValueStreamCapacityPlanner(getUsersForRole(projectReader,
                        valueStreamProject, Role.RESPONSIBLE));
                builder.addValueStreamManager(getUsersForRole(projectReader, valueStreamProject, Role.STEWARD));
                builder.addValueStreamLeader(getUsersForRole(projectReader, valueStreamProject, Role.VS_LEADER));
            }
        }
        return builder.build();
    }

    private Set<String> getUsersForRole(ProjectReader projectReader, Project project, Role role) {
        Project currentProject = project;
        while (currentProject != null) {
            Set<Person> linkedPersons = projectReader.getLinkedPersons(currentProject, role.getKey());
            if (!linkedPersons.isEmpty()) {
                return linkedPersons.stream().map(Person::getLogin).collect(Collectors.toSet());
            }
            currentProject = currentProject.isRoot() ? null : currentProject.getParent();
        }
        return Set.of();
    }

    private class Context {
        final Map<QuotaChangeRequest, Collection<DiQuotaChangeRequest.Permission>> permissions;
        private final Multimap<Long, Long> preOrderIdsByRequestId;
        private final Person performer;
        private final SetMultimap<Long, ProviderAllocationInfo> providersToAllocateByRequest;

        Context(final Collection<QuotaChangeRequest> quotaChangeRequest, final Person performer) {
            final Set<QuotaChangeRequest> resourceRequests = quotaChangeRequest.stream()
                    .filter(request -> request.getType() == QuotaChangeRequest.Type.RESOURCE_PREORDER)
                    .collect(Collectors.toSet());

            providersToAllocateByRequest = requestAllocationManager.providerToAllocate(quotaChangeRequest, performer);
            permissions = new HashMap<>();
            for (final QuotaChangeRequest request : quotaChangeRequest) {
                final Set<ProviderAllocationInfo> providerAllocationInfos = providersToAllocateByRequest.get(request.getId());
                permissions.put(request, getPermissions(request, performer, providerAllocationInfos));
            }

            final List<Long> requestIds = resourceRequests.stream().map(LongIndexBase::getId).collect(Collectors.toList());
            preOrderIdsByRequestId = botPreOrderChangeDao.getPreOrderIdsByRequestId(requestIds);

            this.performer = performer;
        }

        Context(final QuotaChangeRequest quotaChangeRequest, final Person performer) {
            this(Collections.singletonList(quotaChangeRequest), performer);
        }

        public Set<DiQuotaChangeRequest.ProviderAllocationInfo> getProvidersToAllocate(
                QuotaChangeRequest request, Set<String> deliverableProvidersKeys) {
            return providersToAllocateByRequest.get(request.getId())
                    .stream()
                    .map(v -> toView(v, deliverableProvidersKeys))
                    .collect(Collectors.toSet());
        }

        private DiQuotaChangeRequest.ProviderAllocationInfo toView(ProviderAllocationInfo providerAllocationInfo,
                                                                   Set<String> deliverableProvidersKeys) {
            final String key = providerAllocationInfo.getProvider().getKey();
            final Set<DiQuotaChangeRequest.Permission> permissions = new HashSet<>();
            if (providerAllocationInfo.canAllocate()) {
                permissions.add(DiQuotaChangeRequest.Permission.CAN_ALLOCATE_QUOTA);
            }
            final Locale locale = Session.USER_LOCALE.get();
            final List<String> notes = providerAllocationInfo.getNotes()
                    .stream()
                    .map(note -> LocalizationUtils.resolveWithDefaultAsKey(errorMessageSource, note, locale))
                    .collect(Collectors.toList());
            boolean deliverable = deliverableProvidersKeys.contains(key);
            return new DiQuotaChangeRequest.ProviderAllocationInfo(key, permissions, notes,
                    providerAllocationInfo.hasAllocatingResources(), deliverable);
        }

        public Collection<QuotaChangeRequest.Status> getStatuses(final QuotaChangeRequest quotaChangeRequest, final RequestContext requestContext) {

            final QuotaChangeRequest.Type type = quotaChangeRequest.getType();

            if (type != QuotaChangeRequest.Type.RESOURCE_PREORDER) {
                return Set.of();
            }
            final ResourceWorkflow workflow = quotaRequestWorkflowManager.getResourceWorkflow();
            return workflow.getValidStatuses(quotaChangeRequest, requestContext);
        }

        public Collection<DiQuotaChangeRequest.Permission> getPermissions(final QuotaChangeRequest quotaChangeRequest,
                                                                          final Person performer,
                                                                          final Set<ProviderAllocationInfo> providerAllocationInfos) {
            final RequestContext requestContext = new RequestContext(performer, quotaChangeRequest);

            final Collection<QuotaChangeRequest.Status> statuses = getStatuses(quotaChangeRequest, requestContext);

            final Collection<DiQuotaChangeRequest.Permission> permissionsByRequest = new HashSet<>();

            final QuotaChangeRequest.Type type = quotaChangeRequest.getType();

            if (type != QuotaChangeRequest.Type.RESOURCE_PREORDER) {
                return Set.of();
            }

            final ResourceWorkflow workflow = quotaRequestWorkflowManager.getResourceWorkflow();

            for (final QuotaChangeRequest.Status status : statuses) {
                permissionsByRequest.add(status.getPermissionView());
            }

            if (!workflow.canUserUpdateRequest(quotaChangeRequest, requestContext).hasErrors()
                    && !workflow.canRequestBeUpdated(quotaChangeRequest).hasErrors()) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_EDIT);
            }

            if (!workflow.canUserUpdateRequest(quotaChangeRequest, requestContext).hasErrors()
                    && !workflow.canRequestBeUpdated(quotaChangeRequest).hasErrors()
                    && !workflow.canReviewPopupBeUpdated(quotaChangeRequest).hasErrors()) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_EDIT_REVIEW_POPUP);
            }

            if (providerAllocationInfos.stream().anyMatch(ProviderAllocationInfo::canAllocate)) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_ALLOCATE_QUOTA);
            }

            if (ResourceWorkflow.canUserViewBotMoney(performer)) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_VIEW_BOT_PREORDER_COSTS);
            }

            if (ResourceWorkflow.canUserViewMoney(performer)) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_VIEW_OWNING_COST);
            }

            if (ResourceRequestAllocationManager.canUserAllocateQuota(quotaChangeRequest, performer)) {
                permissionsByRequest.add(DiQuotaChangeRequest.Permission.CAN_CLOSE_ALLOCATION_NOTE);
            }

            return permissionsByRequest;
        }

        public Person getPerformer() {
            return performer;
        }
    }

}
