package ru.yandex.direct.web.entity.inventori.service;

import java.util.Map;
import java.util.function.Function;

import javax.annotation.ParametersAreNonnullByDefault;

import one.util.streamex.StreamEx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.core.entity.campaign.model.Campaign;
import ru.yandex.direct.core.entity.campaign.repository.CampaignRepository;
import ru.yandex.direct.core.entity.campaign.service.accesschecker.CampaignSubObjectAccessChecker;
import ru.yandex.direct.core.entity.campaign.service.accesschecker.CampaignSubObjectAccessCheckerFactory;
import ru.yandex.direct.core.entity.campaign.service.validation.CampaignAccessType;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.dbutil.sharding.ShardHelper;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.Defect;
import ru.yandex.direct.validation.result.ValidationResult;
import ru.yandex.direct.web.core.entity.inventori.model.CpmForecastRequest;
import ru.yandex.direct.web.core.entity.inventori.model.GeneralCpmRecommendationRequest;
import ru.yandex.direct.web.core.entity.inventori.validation.InventoriConstraints;

import static java.util.Collections.singletonList;
import static ru.yandex.direct.validation.constraint.CommonConstraints.isNull;
import static ru.yandex.direct.validation.constraint.CommonConstraints.notNull;
import static ru.yandex.direct.validation.constraint.CommonConstraints.validId;
import static ru.yandex.direct.web.core.entity.inventori.validation.InventoriConstraints.validCampaignType;
import static ru.yandex.direct.web.core.entity.inventori.validation.InventoriConstraints.validStrategy;

@ParametersAreNonnullByDefault
@Component
public class CampaignForecastValidationService {

    private final CampaignSubObjectAccessCheckerFactory accessCheckerFactory;
    private final CampaignRepository campaignRepository;
    private final ShardHelper shardHelper;

    @Autowired
    public CampaignForecastValidationService(CampaignSubObjectAccessCheckerFactory accessCheckerFactory,
                                             CampaignRepository campaignRepository, ShardHelper shardHelper) {
        this.accessCheckerFactory = accessCheckerFactory;
        this.campaignRepository = campaignRepository;
        this.shardHelper = shardHelper;
    }

    /**
     * Валидирует запрос на получение прогноза по кампании.
     *
     * @param request     запрос
     * @param clientId    id клиента
     * @param operatorUid uid оператора
     * @return результат валидации
     */
    public ValidationResult<CpmForecastRequest, Defect> validateCampaignForecastRequest(CpmForecastRequest request,
            long operatorUid,
            ClientId clientId) {

        ItemValidationBuilder<CpmForecastRequest, Defect> vb = ItemValidationBuilder.of(request);

        vb.item(request.getCampaignId(), "campaign_id")
                .check(notNull(), When.isTrue(request.getNewCampaignExampleType() == null))
                .check(isNull(), When.isTrue(request.getNewCampaignExampleType() != null))
                .check(validId());

        InventoriConstraints.validateCommon(request, vb);

        vb.item(request.getStrategy(), "strategy")
                .check(notNull())
                .checkBy(strategy -> InventoriConstraints.validateStrategy(strategy, false), When.isValid());

        vb.item(request.getTrafficTypeCorrections(), "traffic_type_corrections")
                .checkBy(InventoriConstraints::validateCorrection, When.isValidAnd(When.notNull()));

        if (request.getCampaignId() != null) {
            CampaignSubObjectAccessChecker checker = accessCheckerFactory.newCampaignChecker(
                    operatorUid, clientId, singletonList(request.getCampaignId()));
            vb.item(request.getCampaignId(), "campaign_id")
                    .checkBy(checker.createValidator(CampaignAccessType.READ), When.isValid());
        }

        return vb.getResult();
    }

    /**
     * Валидирует запрос на получение прогноза по кампании.
     *
     * @param request     запрос
     * @param clientId    id клиента
     * @param operatorUid uid оператора
     * @return результат валидации
     */
    public ValidationResult<GeneralCpmRecommendationRequest, Defect> validateCampaignForecastRequest(
            GeneralCpmRecommendationRequest request,
            long operatorUid,
            ClientId clientId) {

        ItemValidationBuilder<GeneralCpmRecommendationRequest, Defect> vb = ItemValidationBuilder.of(request);

        vb.item(request.getCampaignId(), "campaign_id")
                .check(validId(), When.notNull());

        vb.item(request.getCpmCampaignType(), "campaign_type")
                .check(notNull(), When.isTrue(request.getCampaignId() == null))
                .check(isNull(), When.isTrue(request.getCampaignId() != null));

        vb.item(request.getStrategy(), "strategy")
                .check(notNull())
                .checkBy(strategy -> InventoriConstraints.validateStrategy(strategy, false), When.isValid());

        if (request.getCampaignId() != null) {
            CampaignSubObjectAccessChecker checker = accessCheckerFactory.newCampaignChecker(
                    operatorUid, clientId, singletonList(request.getCampaignId()));
            vb.item(request.getCampaignId(), "campaign_id")
                    .checkBy(checker.createValidator(CampaignAccessType.READ), When.isValid());
        }

        return vb.getResult();
    }


    /**
     * Валидирует запрос на получение цвета светофора и рекомендуемой цены.
     *
     * @param request     запрос
     * @param clientId    id клиента
     * @param operatorUid uid оператора
     * @return результат валидации
     */
    public ValidationResult<CpmForecastRequest, Defect> validateCpmTrafficLightPredictionRequest(CpmForecastRequest request,
                                                                                                 ClientId clientId, Long operatorUid) {
        ItemValidationBuilder<CpmForecastRequest, Defect> vb = ItemValidationBuilder.of(request);

        ItemValidationBuilder<Long, Defect> ivb = vb.item(request.getCampaignId(), "campaign_id");

        ivb.check(notNull(), When.isTrue(request.getNewCampaignExampleType() == null))
                .check(isNull(), When.isTrue(request.getNewCampaignExampleType() != null))
                .check(validId());

        if (request.getCampaignId() != null) {
            CampaignSubObjectAccessChecker checker = accessCheckerFactory.newCampaignChecker(
                    operatorUid, clientId, singletonList(request.getCampaignId()));

            ivb.checkBy(checker.createValidator(CampaignAccessType.READ), When.isValid());

            int shard = shardHelper.getShardByClientId(clientId);
            Map<Long, Campaign> campaignById =
                    StreamEx.of(campaignRepository.getCampaigns(shard, singletonList(request.getCampaignId())))
                            .mapToEntry(Campaign::getId, Function.identity())
                            .toMap();

            ivb.check(validCampaignType(campaignById), When.isValid());
            ivb.check(validStrategy(campaignById), When.isValidAnd(When.isTrue(request.getStrategy() == null)));
        }

        InventoriConstraints.validateCommon(request, vb);

        vb.item(request.getStrategy(), "strategy")
                .check(notNull(), When.isTrue(request.getCampaignId() == null))
                .checkBy(strategy -> InventoriConstraints.validateStrategy(strategy, true), When.isTrue(request.getStrategy() != null));

        return vb.getResult();
    }
}
