package ru.yandex.direct.api.v5.entity.keywords.delegate;

import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import com.google.common.base.Preconditions;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.common.validation.DefectPresentationsHolder;
import ru.yandex.direct.api.v5.converter.ResultConverter;
import ru.yandex.direct.api.v5.entity.keywords.KeywordsDefectTypes;
import ru.yandex.direct.api.v5.entity.keywords.KeywordsEndpoint;
import ru.yandex.direct.api.v5.entity.keywords.container.UpdateInputItem;
import ru.yandex.direct.api.v5.result.ApiMassResult;
import ru.yandex.direct.api.v5.result.ApiResult;
import ru.yandex.direct.api.v5.security.ApiAuthenticationSource;
import ru.yandex.direct.core.entity.client.model.Client;
import ru.yandex.direct.core.entity.client.service.ClientService;
import ru.yandex.direct.core.entity.keyword.container.UpdatedKeywordInfo;
import ru.yandex.direct.core.entity.keyword.model.Keyword;
import ru.yandex.direct.core.entity.keyword.service.KeywordOperationFactory;
import ru.yandex.direct.core.entity.keyword.service.KeywordsUpdateOperation;
import ru.yandex.direct.core.entity.keyword.service.validation.KeywordDefectIds;
import ru.yandex.direct.core.entity.relevancematch.model.RelevanceMatch;
import ru.yandex.direct.core.entity.relevancematch.service.RelevanceMatchService;
import ru.yandex.direct.core.entity.relevancematch.service.RelevanceMatchUpdateOperation;
import ru.yandex.direct.core.entity.user.model.ApiUser;
import ru.yandex.direct.core.entity.user.model.User;
import ru.yandex.direct.model.ModelChanges;
import ru.yandex.direct.operation.Applicability;
import ru.yandex.direct.operation.ConvertResultOperation;
import ru.yandex.direct.operation.Operation;
import ru.yandex.direct.operation.aggregator.SplitAndMergeOperationAggregator;
import ru.yandex.direct.operation.creator.IgnoreDuplicatesOperationCreator;
import ru.yandex.direct.operation.creator.OperationCreator;
import ru.yandex.direct.result.MassResult;
import ru.yandex.direct.result.ResultConverters;
import ru.yandex.direct.validation.defect.CollectionDefects;
import ru.yandex.direct.validation.result.DefectInfo;

import static ru.yandex.direct.utils.CommonUtils.nvl;
import static ru.yandex.direct.utils.FunctionalUtils.filterList;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;
import static ru.yandex.direct.utils.converter.Converters.nullSafeConverter;
import static ru.yandex.direct.validation.result.PathHelper.field;
import static ru.yandex.direct.validation.result.PathHelper.path;

@Component
public class KeywordsUpdater {
    static final DefectPresentationsHolder KEYWORDS_UPDATER_CUSTOM_DEFECT_PRESENTATIONS =
            DefectPresentationsHolder.builderWithFallback(KeywordsEndpoint.KEYWORDS_CUSTOM_DEFECT_PRESENTATIONS)
                    .register(KeywordDefectIds.Gen.AD_GROUP_NOT_FOUND,
                            KeywordsDefectTypes.keywordNotFound())
                    .build();
    private static final Predicate<DefectInfo<?>> WARNING_NOT_FOR_PRICE_AND_PRIORITY = di ->
            !di.getPath().startsWith(path(field("price")))
                    && !di.getPath().startsWith(path(field("priceContext")))
                    && !di.getPath().startsWith(path(field("autobudgetPriority")));

    private final ApiAuthenticationSource apiAuthenticationSource;
    private final ResultConverter resultConverter;
    private final KeywordOperationFactory keywordOperationFactory;
    private final RelevanceMatchService relevanceMatchService;
    private final ClientService clientService;

    public KeywordsUpdater(ApiAuthenticationSource apiAuthenticationSource,
                           ResultConverter resultConverter,
                           KeywordOperationFactory keywordOperationFactory,
                           RelevanceMatchService relevanceMatchService,
                           ClientService clientService) {
        this.apiAuthenticationSource = apiAuthenticationSource;
        this.resultConverter = resultConverter;
        this.keywordOperationFactory = keywordOperationFactory;
        this.relevanceMatchService = relevanceMatchService;
        this.clientService = clientService;
    }

    ApiMassResult<Long> doUpdate(List<UpdateInputItem> internalRequest) {
        return doUpdate(internalRequest, false, null);
    }

    ApiMassResult<Long> doUpdate(List<UpdateInputItem> internalRequest, boolean addWarningsForDuplicates,
                                 @Nullable DefectPresentationsHolder defectPresentationsHolder) {
        return doUpdate(internalRequest, addWarningsForDuplicates, false, defectPresentationsHolder);
    }

    ApiMassResult<Long> doUpdate(List<UpdateInputItem> internalRequest, boolean addWarningsForDuplicates,
                                 boolean disablePhraseModification,
                                 @Nullable DefectPresentationsHolder defectPresentationsHolder) {
        ApiUser operator = apiAuthenticationSource.getOperator();
        ApiUser targetUser = apiAuthenticationSource.getChiefSubclient();

        OperationCreator<UpdateInputItem, Operation<Long>> updateKeywordCoreOperationCreator =
                inputItems -> new ConvertResultOperation<>(
                        createKeywordUpdateOperation(operator, targetUser,
                                mapList(inputItems, UpdateInputItem::getKeywordChanges), disablePhraseModification),
                        ResultConverters.massResultValueConverter(nullSafeConverter(UpdatedKeywordInfo::getId))
                );
        if (addWarningsForDuplicates) {
            updateKeywordCoreOperationCreator = new IgnoreDuplicatesOperationCreator<>(
                    updateKeywordCoreOperationCreator,
                    Comparator.comparing(UpdateInputItem::getId),
                    result -> result.getValidationResult().addWarning(CollectionDefects.duplicatedObject())
            );
        }

        OperationCreator<UpdateInputItem, Operation<Long>> updateRelevanceMatchCoreOperationCreator =
                inputItems -> createRelevanceMatchUpdateOperation(
                        operator, targetUser,
                        mapList(inputItems, UpdateInputItem::getRelevanceMatchChanges)
                );
        if (addWarningsForDuplicates) {
            updateRelevanceMatchCoreOperationCreator = new IgnoreDuplicatesOperationCreator<>(
                    updateRelevanceMatchCoreOperationCreator,
                    Comparator.comparing(UpdateInputItem::getId),
                    result -> result.getValidationResult().addWarning(CollectionDefects.duplicatedObject())
            );
        }

        SplitAndMergeOperationAggregator<UpdateInputItem, Long> updateOperation =
                SplitAndMergeOperationAggregator.builderForPartialOperations()
                        .addSubOperation(
                                UpdateInputItem::hasKeywordChanges,
                                updateKeywordCoreOperationCreator)
                        .addSubOperation(
                                UpdateInputItem::hasRelevanceMatchChanges,
                                updateRelevanceMatchCoreOperationCreator)
                        .build();
        MassResult<Long> aggregateOperationResult = updateOperation.execute(internalRequest);
        ApiMassResult<Long> apiMassResult = resultConverter.toApiMassResult(
                aggregateOperationResult, nvl(defectPresentationsHolder, KEYWORDS_UPDATER_CUSTOM_DEFECT_PRESENTATIONS));
        return ResultConverter.apiMassResultConverter(KeywordsUpdater::apiResultRemovePriceWarnings)
                .convert(apiMassResult);
    }

    private KeywordsUpdateOperation createKeywordUpdateOperation(User operator, User targetUser,
                                                                 List<ModelChanges<Keyword>> keywordChanges,
                                                                 boolean disablePhraseModification)
    {
        return keywordOperationFactory.createKeywordsUpdateOperation(
                Applicability.PARTIAL, keywordChanges, operator.getUid(),
                targetUser.getClientId(), targetUser.getUid(),
                false, disablePhraseModification);
    }

    private RelevanceMatchUpdateOperation createRelevanceMatchUpdateOperation(User operator, User targetUser,
                                                                              List<ModelChanges<RelevanceMatch>> relevanceMatch) {
        Client client = Preconditions.checkNotNull(clientService.getClient(targetUser.getClientId()));
        return relevanceMatchService.createPartialUpdateOperation(
                client.getWorkCurrency().getCurrency(), targetUser.getClientId(), targetUser.getUid(),
                operator.getUid(), relevanceMatch);
    }

    private static ApiResult<Long> apiResultRemovePriceWarnings(ApiResult<Long> result) {
        // Warning'и на ставки выдаются из-за состояния базы и не относятся к обновлению (DIRECT-80646)
        return new ApiResult<>(result.getResult(), result.getErrors(),
                filterList(result.getWarnings(), WARNING_NOT_FOR_PRICE_AND_PRIORITY), result.getState());
    }
}
