package ru.yandex.direct.web.entity.adgroup.service.cpm;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import javax.annotation.ParametersAreNonnullByDefault;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.direct.core.entity.adgroup.model.AdGroupType;
import ru.yandex.direct.core.entity.banner.type.pixels.PixelProvider;
import ru.yandex.direct.core.entity.bids.validation.PriceValidator;
import ru.yandex.direct.core.entity.campaign.model.CampaignType;
import ru.yandex.direct.core.entity.currency.model.cpmyndxfrontpage.CpmYndxFrontpageAdGroupPriceRestrictions;
import ru.yandex.direct.currency.Currency;
import ru.yandex.direct.validation.builder.Constraint;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.ListValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.defect.CommonDefects;
import ru.yandex.direct.validation.result.Defect;
import ru.yandex.direct.validation.result.ValidationResult;
import ru.yandex.direct.validation.wrapper.ModelItemValidationBuilder;
import ru.yandex.direct.web.entity.adgroup.converter.AdGroupConverterUtils;
import ru.yandex.direct.web.entity.adgroup.model.PixelKind;
import ru.yandex.direct.web.entity.adgroup.model.WebAutoPrice;
import ru.yandex.direct.web.entity.adgroup.model.WebCpmAdGroup;
import ru.yandex.direct.web.entity.adgroup.model.WebCpmAdGroupRetargeting;
import ru.yandex.direct.web.entity.banner.model.WebCpmBanner;
import ru.yandex.direct.web.entity.banner.model.WebPixel;

import static com.google.common.base.Preconditions.checkNotNull;
import static ru.yandex.direct.core.entity.banner.service.validation.defects.BannerDefects.invalidPixelFormat;
import static ru.yandex.direct.validation.constraint.CommonConstraints.notNull;

@Service
public class WebCpmAdGroupValidationService {

    @Autowired
    public WebCpmAdGroupValidationService() {
    }

    public ValidationResult<List<WebCpmAdGroup>, Defect> validate(List<WebCpmAdGroup> webAdGroups,
                                                                  ValidationData validationData) {
        return ListValidationBuilder.of(webAdGroups, Defect.class)
                .checkEach(notNull())
                .checkEachBy((adGroupIndex, webAdGroup) -> validateWebAdGroup(webAdGroup, adGroupIndex, validationData),
                        When.isValid())
                .getResult();
    }

    private ValidationResult<WebCpmAdGroup, Defect> validateWebAdGroup(WebCpmAdGroup webAdGroup,
                                                                       Integer adGroupIndex, ValidationData validationData) {
        ItemValidationBuilder<WebCpmAdGroup, Defect> vb = ItemValidationBuilder.of(webAdGroup, Defect.class);

        boolean hasRetargetings = webAdGroup.getRetargetings() != null;
        AdGroupType adGroupType = AdGroupConverterUtils.extractCpmAdGroupType(webAdGroup, validationData.campaignType);

        if (!validationData.autoBudget) {
            if (hasRetargetings) {
                vb.list(webAdGroup.getRetargetings(), WebCpmAdGroup.Prop.RETARGETINGS)
                        .checkEachBy(retargeting -> validateRetargetings(retargeting, adGroupIndex, validationData,
                                adGroupType));
            } else {
                vb.item(webAdGroup.getAutoPrice(), WebCpmAdGroup.Prop.AUTO_PRICE)
                        .check(notNull())
                        .checkBy(autoPrice -> validateAutoPrice(autoPrice, adGroupIndex, validationData, adGroupType),
                                When.isValid());
            }
        }

        vb.list(webAdGroup.getBanners(), WebCpmAdGroup.Prop.BANNERS)
                .checkEachBy(WebCpmAdGroupValidationService::validateWebBanner);

        return vb.getResult();
    }

    private ValidationResult<WebCpmAdGroupRetargeting, Defect> validateRetargetings(
            WebCpmAdGroupRetargeting retargeting, Integer adGroupIndex, ValidationData validationData,
            AdGroupType adGroupType) {
        ItemValidationBuilder<WebCpmAdGroupRetargeting, Defect> vb = ItemValidationBuilder.of(retargeting);
        if (retargeting == null) {
            return vb.getResult();
        }

        BigDecimal price =
                retargeting.getPriceContext() == null ? null : BigDecimal.valueOf(retargeting.getPriceContext());
        vb.item(price, WebCpmAdGroupRetargeting.Prop.PRICE_CONTEXT)
                .check(notNull())
                .checkBy(new PriceValidator(validationData.clientCurrency, adGroupType,
                        validationData.cpmYndxFrontpageCurrencyAdGroupRestrictions.get(adGroupIndex)), When.isValid());

        return vb.getResult();
    }

    private ValidationResult<WebAutoPrice, Defect> validateAutoPrice(WebAutoPrice autoPrice,
                                                                     Integer adGroupIndex, ValidationData validationData, AdGroupType adGroupType) {
        ItemValidationBuilder<WebAutoPrice, Defect> vb = ItemValidationBuilder.of(autoPrice);


        BigDecimal price = autoPrice.getGeneralPrice() == null ? null : BigDecimal.valueOf(autoPrice.getGeneralPrice());
        vb.item(price, WebAutoPrice.Prop.GENERAL_PRICE)
                .checkBy(new PriceValidator(validationData.clientCurrency, adGroupType,
                        validationData.cpmYndxFrontpageCurrencyAdGroupRestrictions.get(adGroupIndex)), When.notNull());

        return vb.getResult();
    }

    private static ValidationResult<WebCpmBanner, Defect> validateWebBanner(WebCpmBanner banner) {
        ItemValidationBuilder<WebCpmBanner, Defect> vb = ModelItemValidationBuilder.of(banner);

        vb.list(banner.getPixels(), WebCpmBanner.Prop.PIXELS)
                .checkEachBy(WebCpmAdGroupValidationService::validateBannerPixel);

        return vb.getResult();
    }

    private static ValidationResult<WebPixel, Defect> validateBannerPixel(WebPixel webPixel) {
        return ItemValidationBuilder.<WebPixel, Defect>of(webPixel)
                .check(pixelNotNull())
                .check(validPixelFormat(), When.isValid())
                .check(pixelUrlCorrespondsToItsKind(), When.isValid())
                .getResult();
    }

    /**
     * Комплексная проверка, что все части пикселя не null. Не имеет смысла разделять, так как со стороны интерфейса
     * не должно приходить null'ов в пикслеле
     */
    private static Constraint<WebPixel, Defect> pixelNotNull() {
        return webPixel -> webPixel != null && webPixel.getKind() != null && webPixel.getUrl() != null ? null :
                CommonDefects.notNull();
    }

    private static Constraint<WebPixel, Defect> validPixelFormat() {
        return webPixel -> PixelProvider.fromUrl(webPixel.getUrl()) != null ? null : invalidPixelFormat();
    }

    private static Constraint<WebPixel, Defect> pixelUrlCorrespondsToItsKind() {
        return Constraint.fromPredicate(webPixel -> {
            PixelProvider pixelProvider = PixelProvider.fromUrl(webPixel.getUrl());
            PixelKind kind = webPixel.getKind();
            switch (kind) {
                case AUDIENCE:
                    if (pixelProvider == PixelProvider.YANDEXAUDIENCE) {
                        return true;
                    }
                    break;
                case AUDIT:
                    if (pixelProvider != PixelProvider.YANDEXAUDIENCE) {
                        return true;
                    }
                    break;
                default:
                    throw new UnsupportedOperationException("Pixel kind is not supported");
            }
            return false;
        }, invalidPixelFormat());
    }

    @ParametersAreNonnullByDefault
    public static class ValidationData {
        private Currency clientCurrency;
        private boolean autoBudget;
        private CampaignType campaignType;
        private Map<Integer, CpmYndxFrontpageAdGroupPriceRestrictions> cpmYndxFrontpageCurrencyAdGroupRestrictions;

        public ValidationData(Currency clientCurrency, boolean autoBudget,
                              CampaignType campaignType,
                              Map<Integer, CpmYndxFrontpageAdGroupPriceRestrictions> cpmYndxFrontpageCurrencyAdGroupRestrictions) {
            checkNotNull(clientCurrency);
            checkNotNull(campaignType);
            checkNotNull(cpmYndxFrontpageCurrencyAdGroupRestrictions);
            this.clientCurrency = clientCurrency;
            this.autoBudget = autoBudget;
            this.campaignType = campaignType;
            this.cpmYndxFrontpageCurrencyAdGroupRestrictions = cpmYndxFrontpageCurrencyAdGroupRestrictions;
        }
    }
}
