package ru.yandex.webmaster3.storage.turbo.service.settings;

import java.net.URI;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.Mutable;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.turbo.TurboConstants;
import ru.yandex.webmaster3.core.turbo.model.TurboDisplaySettings;
import ru.yandex.webmaster3.core.turbo.model.TurboHostHeader;
import ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType;
import ru.yandex.webmaster3.core.turbo.model.TurboHostSettings;
import ru.yandex.webmaster3.core.turbo.model.TurboHostSettings.TurboHostSettingsBuilder;
import ru.yandex.webmaster3.core.turbo.model.TurboHostSettingsBlock;
import ru.yandex.webmaster3.core.turbo.model.TurboLogo;
import ru.yandex.webmaster3.core.turbo.model.TurboPlatform;
import ru.yandex.webmaster3.core.turbo.model.TurboUserAgreement;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingNetworkType;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingPlacement;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingSettings;
import ru.yandex.webmaster3.core.turbo.model.analytics.AnalyticsSettings;
import ru.yandex.webmaster3.core.turbo.model.analytics.AnalyticsSystemType;
import ru.yandex.webmaster3.core.turbo.model.authorization.TurboAuthorizationSettings;
import ru.yandex.webmaster3.core.turbo.model.comments.TurboCommentsSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.PaymentMethodEnum;
import ru.yandex.webmaster3.core.turbo.model.commerce.PaymentMethodSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboColorScheme;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboCommerceSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboCommerceSettingsBlock;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboNewCommerceSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboNewCommerceSettings.TurboNewCommerceSettingsBuilder;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboPaymentsSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.delivery.exception.DeliverySettingsException;
import ru.yandex.webmaster3.core.turbo.model.desktop.TurboDesktopSettings;
import ru.yandex.webmaster3.core.turbo.model.desktop.TurboDesktopSettings.TurboDesktopSettingsBuilder;
import ru.yandex.webmaster3.core.turbo.model.desktop.TurboDesktopSettingsBlock;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedType;
import ru.yandex.webmaster3.core.turbo.model.feedback.TurboFeedbackSettings;
import ru.yandex.webmaster3.core.turbo.model.menu.TurboMenuItem;
import ru.yandex.webmaster3.core.turbo.model.search.TurboSearchSettings;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.WwwUtil;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.dao.AbtHostExperimentYDao;
import ru.yandex.webmaster3.storage.abt.model.ExperimentInfo;
import ru.yandex.webmaster3.storage.abt.model.ExperimentScope;
import ru.yandex.webmaster3.storage.turbo.logo.ProcessTurboLogoService;
import ru.yandex.webmaster3.storage.turbo.logo.TurboLogoData;
import ru.yandex.webmaster3.storage.turbo.service.TurboFeedsService;
import ru.yandex.webmaster3.storage.turbo.service.css.TurboCssValidatorResponse;
import ru.yandex.webmaster3.storage.turbo.service.validation.TurboParserException;
import ru.yandex.webmaster3.storage.turbo.service.validation.TurboParserService;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.util.uri.WebmasterUriUtils;

import static ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType.HORIZONTAL;
import static ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType.NOLOGO;
import static ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType.SQUARE;
import static ru.yandex.webmaster3.storage.abt.model.Experiment.TURBO_NEW_PAYMENTS_METHODS;
import static ru.yandex.webmaster3.storage.abt.model.Experiment.TURN_OFF_COMMENTATOR;

/**
 * Created by Oleg Bazdyrev on 22/01/2020.
 */
@Service("turboSettingsMergerService")
@AllArgsConstructor(onConstructor_ = @Autowired)
public class TurboSettingsMergerService {

    private static final Predicate<String> COLOR_PATTERN = Pattern.compile("[a-fA-F0-9]{6}").asMatchPredicate();

    private final ExperimentInfo ANTI_ADBLOCK_EXPERIMENT = new ExperimentInfo("TURBO_ANTIADBLOCK_MESSAGE", ExperimentScope.DOMAIN.toString());
    private final ExperimentInfo TURBO_REMOVE_METRIKA_MESSAGE = new ExperimentInfo("TURBO_REMOVE_METRIKA_MESSAGE", "MAIN");

    private final AbtService abtService;
    private final ProcessTurboLogoService processTurboLogoService;
    private final TurboFeedsService turboFeedsService;
    private final TurboParserService turboParserService;
    private final TurboSettingsService turboSettingsService;
    private final AbtHostExperimentYDao abtHostExperimentYDao;

    // TODO необходимо избавиться от hostId в пользу domain
    public TurboHostSettingsBuilder mergeHostSettings(WebmasterHostId hostId, Long userId, TurboHostSettings newSettings, Set<TurboHostSettingsBlock> blocks,
                                                      boolean throwOnError, Mutable<TurboCssValidatorResponse> cssResponse) {
        String domain = WwwUtil.cutWWWAndM(hostId);
        TurboHostSettings oldSettings = turboSettingsService.getSettings(domain);
        TurboHostSettingsBuilder settingsBuilder = new TurboHostSettingsBuilder(oldSettings);
        if (!blocks.isEmpty() && newSettings == null) {
            newSettings = new TurboHostSettingsBuilder().build();
        }

        // мержим изменения
        // заголовок и логотип
        if (blocks.contains(TurboHostSettingsBlock.HEADER)) {
            // сохраним логотип
            settingsBuilder.setHeader(saveHeader(hostId, newSettings.getHeader(), TurboPlatform.MOBILE, throwOnError));
        }
        // реклама
        if (blocks.contains(TurboHostSettingsBlock.ADVERTISING)) {
            settingsBuilder.setAdvertisingSettings(validateAdvertistingSettings(hostId, newSettings.getAdvertisingSettings(), false, throwOnError));
        }
        // аналитика
        if (blocks.contains(TurboHostSettingsBlock.ANALYTICS)) {
            abtHostExperimentYDao.delete(IdUtils.urlToHostId(domain), TURBO_REMOVE_METRIKA_MESSAGE);
            settingsBuilder.setAnalyticsSettings(validateAnalyticsSettings(newSettings, throwOnError));
        }
        // доступ
        if (blocks.contains(TurboHostSettingsBlock.ACCESS)) {
            settingsBuilder.setAccessSettings(newSettings.getAccessSettings());
        }
        // меню
        if (blocks.contains(TurboHostSettingsBlock.MENU)) {
            settingsBuilder.setMenuSettings(validateMenuSettings(newSettings.getMenuSettings(), TurboHostSettingsBlock.MENU, throwOnError));
        }
        // user agreement
        if (blocks.contains(TurboHostSettingsBlock.USER_AGREEMENT)) {
            settingsBuilder.setUserAgreement(validateUserAgreement(newSettings.getUserAgreement(), throwOnError));
        }
        // feedback
        if (blocks.contains(TurboHostSettingsBlock.FEEDBACK)) {
            settingsBuilder.setFeedbackSettings(validateFeedbackSettings(hostId, newSettings.getFeedbackSettings(), throwOnError));
        }
        // autorelated
        if (blocks.contains(TurboHostSettingsBlock.AUTO_RELATED)) {
            settingsBuilder.setAutoRelated(newSettings.getAutoRelated());
            settingsBuilder.setAutoRelatedSettings(newSettings.getAutoRelatedSettings());
        }
        // autorelated
        if (blocks.contains(TurboHostSettingsBlock.AUTO_MORDA)) {
            settingsBuilder.setAutoMorda(newSettings.getAutoMorda());
        }
        if (blocks.contains(TurboHostSettingsBlock.CSS)) {
            TurboCssValidatorResponse cssValidateResponse = turboParserService.validateCss(newSettings.getCss());
            if (cssValidateResponse.isSuccess()) {
                settingsBuilder.setCss(newSettings.getCss(), cssValidateResponse.getResult());
            } else if (throwOnError) {
                throw new WebmasterException("Invalid CSS", new SetTurboSettingsResponse.InvalidCssResponse(cssValidateResponse.createErrorInfos()));
            } else if (cssResponse != null) {
                cssResponse.setValue(cssValidateResponse);
            }
        }
        // e-commerce
        if (CollectionUtils.containsAny(blocks, TurboHostSettingsBlock.COMMERCE)) {
            TurboCommerceSettings.TurboCommerceSettingsBuilder csb = new TurboCommerceSettings.TurboCommerceSettingsBuilder(oldSettings.getCommerceSettings());
            csb.copyCommerceSettings(newSettings.getCommerceSettings(), blocks);
            // синхронизация оплат, старого и нового интерфейса,
            // чтобы турбо могли брать любой формат, и мы могли раскатывать плавно эксп
            // изначально синхронизирую с помощью coord
            if (blocks.contains(TurboHostSettingsBlock.PAYMENTS)) {
                if (csb.getPaymentsSettings() != null) {
                    TurboPaymentsSettings turboPaymentsSettings = csb.getPaymentsSettings();
                    if (abtService.isInExperiment(hostId, TURBO_NEW_PAYMENTS_METHODS)) {
                        final boolean enabledOnline = turboPaymentsSettings.getPaymentMethods().stream().filter(x -> x.getName() == PaymentMethodEnum.ONLINE).findAny().map(PaymentMethodSettings::isEnabled).orElse(false);
                        csb.setPaymentsSettings(turboPaymentsSettings.withEnabled(enabledOnline));
                    } else {
                        final boolean enabledOnline = turboPaymentsSettings.isEnabled();
                        final PaymentMethodSettings newPaymentMethod = turboPaymentsSettings.getPaymentMethods().stream()
                                .filter(x -> x.getName() == PaymentMethodEnum.ONLINE)
                                .findAny()
                                .orElse(PaymentMethodSettings.createDefault(PaymentMethodEnum.ONLINE, enabledOnline))
                                .withEnabled(enabledOnline);
                        final HashSet<PaymentMethodSettings> newPaymentMethodSettings = new HashSet<>(turboPaymentsSettings.getPaymentMethods());
                        newPaymentMethodSettings.remove(newPaymentMethod);
                        newPaymentMethodSettings.add(newPaymentMethod);
                        csb.setPaymentsSettings(turboPaymentsSettings.withPaymentMethods(newPaymentMethodSettings));
                    }
                }
            }

            settingsBuilder.setCommerceSettings(validateCommerceSettings(hostId, csb.build(), userId, blocks, throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.SEARCH)) {
            settingsBuilder.setSearchSettings(validateSearchSettings(hostId, newSettings.getSearchSettings(), throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.TOP_MENU)) {
            settingsBuilder.setTopMenuSettings(validateMenuSettings(newSettings.getTopMenuSettings(), TurboHostSettingsBlock.TOP_MENU, throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.AUTHORIZATION)) {
            settingsBuilder.setAuthorizationSettings(validateAuthorizationSettings(newSettings.getAuthorizationSettings(), throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.COMMENTS)) {
            settingsBuilder.setCommentsSettings(validateCommentsSettings(domain, newSettings.getCommentsSettings(), throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.AUTO_MENU)) {
            settingsBuilder.setAutoMenuSettings(validateMenuSettings(newSettings.getAutoMenuSettings(), TurboHostSettingsBlock.AUTO_MENU, throwOnError));
        }
        if (blocks.contains(TurboHostSettingsBlock.DISPLAY)) {
            settingsBuilder.setDisplaySettings(validateDisplaySettings(newSettings.getDisplaySettings(), throwOnError));
            // header
            settingsBuilder.setHeader(saveHeader(hostId, newSettings.getDisplaySettings().getHeader(), TurboPlatform.MOBILE, throwOnError));
        }
        return settingsBuilder;
    }

    public TurboDesktopSettingsBuilder mergeDesktopSettings(WebmasterHostId hostId, Long userId, TurboDesktopSettings newSettings,
                                                            Set<TurboDesktopSettingsBlock> blocks, boolean throwOnError,
                                                            Mutable<TurboCssValidatorResponse> cssResponse) {
        String domain = WwwUtil.cutWWWAndM(hostId);
        TurboDesktopSettings oldSettings = turboSettingsService.getDesktopSettings(domain);
        TurboDesktopSettingsBuilder settingsBuilder = new TurboDesktopSettingsBuilder(oldSettings);
        if (!blocks.isEmpty() && newSettings == null) {
            newSettings = new TurboDesktopSettingsBuilder().build();
        }

        // логотип десктопа
        if (blocks.contains(TurboDesktopSettingsBlock.HEADER)) {
            settingsBuilder.setHeader(saveHeader(hostId, newSettings.getHeader(), TurboPlatform.DESKTOP, throwOnError));
        }
        // реклама
        if (blocks.contains(TurboDesktopSettingsBlock.ADVERTISING)) {
            settingsBuilder.setAdvertisingSettings(validateAdvertistingSettings(hostId, newSettings.getAdvertisingSettings(), true, throwOnError));
        }
        if (blocks.contains(TurboDesktopSettingsBlock.ENABLED)) {
            // безумный костыль
            if (throwOnError && Boolean.TRUE.equals(newSettings.getEnabled()) && domainHasActiveYmlFeeds(domain)) {
                throw new WebmasterException("Attempt to turn on desktop for host with active YML feeds",
                        new WebmasterErrorResponse.IllegalParameterValueResponse(getClass(), "enabled", "true"));
            }
            settingsBuilder.setEnabled(newSettings.getEnabled());
        }
        if (blocks.contains(TurboDesktopSettingsBlock.COLUMNS)) {
            settingsBuilder.setColumns(newSettings.getColumns());
        }
        if (blocks.contains(TurboDesktopSettingsBlock.CSS)) {
            TurboCssValidatorResponse cssValidateResponse = turboParserService.validateCss(newSettings.getCss());
            if (cssValidateResponse.isSuccess()) {
                settingsBuilder.setCss(newSettings.getCss()).setMinifiedCss(cssValidateResponse.getResult());
            } else if (throwOnError) {
                throw new WebmasterException("Invalid desktop CSS", new SetTurboSettingsResponse.InvalidCssResponse(cssValidateResponse.createErrorInfos()));
            } else if (cssResponse != null) {
                cssResponse.setValue(cssValidateResponse);
            }
        }
        if (blocks.contains(TurboDesktopSettingsBlock.ANTI_ADBLOCK)) {
            settingsBuilder.setAabEnabled(newSettings.getAabEnabled());
            settingsBuilder.setAntiItpEnabled(newSettings.getAntiItpEnabled());

            // WMC-9538 remove after experiment end
            if (Boolean.TRUE.equals(newSettings.getAabEnabled())) {
                abtHostExperimentYDao.delete(hostId, ANTI_ADBLOCK_EXPERIMENT);
            }
        }
        if (blocks.contains(TurboDesktopSettingsBlock.DISPLAY)) {
            settingsBuilder.setDisplaySettings(validateDisplaySettings(newSettings.getDisplaySettings(), throwOnError));
            // header
            settingsBuilder.setHeader(saveHeader(hostId, newSettings.getDisplaySettings().getHeader(), TurboPlatform.DESKTOP, throwOnError));
        }
        return settingsBuilder;
    }

    private TurboHostHeader saveHeader(WebmasterHostId hostId, TurboHostHeader header, TurboPlatform platform, boolean throwOnError) {
        // проверим, что хоть один тип заголовка выбран
        try {
            if (header.getType() == null) {
                throw new WebmasterException("Header not selected", new SetTurboSettingsResponse.HeaderNotSelectedErrorResponse(platform));
            }
            if (header.getType() == NOLOGO) {
                return header;
            }
            return new TurboHostHeader(header.getTitle(), header.getType(), saveLogo(hostId, header.getLogoInfo(), platform), header.getHeaderParams());
        } catch (Exception e) {
            if (throwOnError) {
                throw e;
            }
            return new TurboHostHeader(header.getTitle(), TurboHostHeaderType.NOLOGO, null, header.getHeaderParams());
        }
    }

    private TurboLogo saveLogo(WebmasterHostId hostId, TurboLogo turboLogo, TurboPlatform platform) {
        if (turboLogo.getLogoId() == null) {
            throw new WebmasterException("Logo for selected header type is not set",
                    new SetTurboSettingsResponse.LogoNotSetErrorResponse(platform));
        }
        if (turboLogo.isDefault()) {
            return turboLogo; // нечего сохранять
        }

        TurboLogoData logo = processTurboLogoService.save(hostId, turboLogo.getLogoId());
        if (logo != null) {
            String publicUrl = logo.getPublicUrl().substring(0, logo.getPublicUrl().lastIndexOf("/") + 1);
            turboLogo.getLogoUrls().put(SQUARE, publicUrl + SQUARE.getLogoSize());
            turboLogo.getLogoUrls().put(HORIZONTAL, publicUrl + HORIZONTAL.getLogoSize());
            return turboLogo.toBuilder().width(logo.getWidth()).height(logo.getHeight()).build();
        } else {
            throw new WebmasterException("Unable to save logo for header",
                    new SetTurboSettingsResponse.LogoNotSetErrorResponse(platform));
        }
    }

    public TurboNewCommerceSettingsBuilder mergeCommerceSettings(WebmasterHostId hostId, TurboNewCommerceSettings newSettings,
                                                                 Set<TurboCommerceSettingsBlock> blocks, boolean throwOnError) {

        return mergeCommerceSettings(hostId, newSettings, blocks, throwOnError, null);
    }

    public TurboNewCommerceSettingsBuilder mergeCommerceSettings(WebmasterHostId hostId, TurboNewCommerceSettings newSettings,
                                                                 Set<TurboCommerceSettingsBlock> blocks, boolean throwOnError,
                                                                 TurboHostSettings domainSettings) {
        String domain = WwwUtil.cutWWWAndM(hostId);
        TurboNewCommerceSettings oldSettings;
        if (domainSettings == null) {
            oldSettings = turboSettingsService.getNewCommerceSettings(domain);
        } else {
            oldSettings = turboSettingsService.getNewCommerceSettings(domain, domainSettings);
        }

        var settingsBuilder = oldSettings.toBuilder();
        if (!blocks.isEmpty() && newSettings == null) {
            newSettings = TurboNewCommerceSettings.builder().build();
        }

        // мержим изменения
        if (blocks.contains(TurboCommerceSettingsBlock.HEADER)) {
            // title
            try {
                // TODO ограничения длины названия и описания
                if (StringUtils.isBlank(newSettings.getTitle()) || newSettings.getTitle().length() > 24) {
                    throw new WebmasterException("Invalid title", new SetTurboSettingsResponse.InvalidTitleResponse());
                }
                settingsBuilder.title(newSettings.getTitle());
                // description
                if (StringUtils.isBlank(newSettings.getDescription()) || newSettings.getDescription().length() > 32) {
                    throw new WebmasterException("Invalid description", new SetTurboSettingsResponse.InvalidDescriptionResponse());
                }
                settingsBuilder.description(newSettings.getDescription());
                if (newSettings.getTheme() == null || newSettings.getTheme().getType() == null) {
                    throw invalidParamValue("Empty theme", "theme", null);
                }
                // check colors
                if (newSettings.getTheme().getType() == TurboColorScheme.Type.CUSTOM) {
                    validateColor(newSettings.getTheme().getColorActionButtonText(), "colorActionButtonText");
                    validateColor(newSettings.getTheme().getColorBrand(), "colorBrand");
                    validateColor(newSettings.getTheme().getColorBottombarActive(), "colorBottombarActive");
                    validateColor(newSettings.getTheme().getColorDiscount(), "colorDiscount");
                }
                settingsBuilder.theme(newSettings.getTheme());
                if (newSettings.getLogoInfo() == null) {
                    throw new WebmasterException("Logo not set", new SetTurboSettingsResponse.LogoNotSetErrorResponse(TurboPlatform.MOBILE));
                }
                TurboLogo logoInfo = saveLogo(hostId, newSettings.getLogoInfo(), TurboPlatform.MOBILE);
                int height = ObjectUtils.firstNonNull(logoInfo.getHeight(), TurboConstants.MIN_FAVICON_SIZE_FOR_LOGO);
                int width = ObjectUtils.firstNonNull(logoInfo.getWidth(), TurboConstants.MIN_FAVICON_SIZE_FOR_LOGO);
                if (height < TurboConstants.MIN_FAVICON_SIZE_FOR_LOGO || width < TurboConstants.MIN_FAVICON_SIZE_FOR_LOGO ||
                        ((double) Math.abs(height - width)) / Math.min(height, width) > 0.05) {
                    throw new WebmasterException("Invalid logo", new SetTurboSettingsResponse.InvalidLogoResponse());
                }
                settingsBuilder.logoInfo(logoInfo);
                settingsBuilder.marketRatingShow(newSettings.getMarketRatingShow());
            } catch (Exception e) {
                if (throwOnError) {
                    throw e;
                }
                // ignoring for preview
            }
        }

        if (blocks.contains(TurboCommerceSettingsBlock.INFOSECTIONS)) {
            try {
                turboParserService.validateInfoSections(newSettings.getInfoSections());
                settingsBuilder.infoSections(newSettings.getInfoSections());
            } catch (TurboParserException e) {
                if (e.getCode() == TurboParserException.ErrorCode.DUPLICATE_COMMERCE_SECTION) {
                    if (throwOnError) {
                        throw new WebmasterException("Duplicate commerce section",
                                new SetTurboSettingsResponse.DuplicateCommerceSectionResponse(e.getIndex()), e);
                    }
                } else {
                    throw new IllegalStateException();
                }
            }
        }

        if (blocks.contains(TurboCommerceSettingsBlock.FEEDBACK)) {
            String email = newSettings.getFeedbackEmail();
            String phone = newSettings.getFeedbackPhone();
            try {
                if (!Strings.isNullOrEmpty(email)) {
                    email = TurboConstants.EMAIL_SCHEME + TurboParserService.validateEmail(email, 0);
                }
                if (!Strings.isNullOrEmpty(phone)) {
                    phone = TurboConstants.PHONE_SCHEME + TurboParserService.validatePhoneNumber(phone, 0);
                }

            } catch (TurboParserException e) {
                if (throwOnError) {
                    switch (e.getCode()) {
                        case INVALID_FEEDBACK_URL:
                            throw new WebmasterException("Invalid feedback url", new SetTurboSettingsResponse.InvalidFeedbackUrlResponse(e.getIndex()), e);
                        case INVALID_FEEDBACK_EMAIL:
                            throw new WebmasterException("Invalid feedback email", new SetTurboSettingsResponse.InvalidFeedbackEmailResponse(e.getIndex()), e);
                    }
                    throw new IllegalStateException("Unexpected code: " + e.getCode().name());
                }
            }
            settingsBuilder.feedbackPhone(phone);
            settingsBuilder.feedbackEmail(email);
        }

        return settingsBuilder;
    }

    @NotNull
    private List<AdvertisingSettings> validateAdvertistingSettings(
            WebmasterHostId hostId, Map<AdvertisingPlacement, List<AdvertisingSettings>> settingsMap, boolean desktop, boolean throwOnError) {

        TurboPlatform platform = desktop ? TurboPlatform.DESKTOP : TurboPlatform.MOBILE;
        List<AdvertisingSettings> parsedAdSettings = new ArrayList<>();
        Set<String> aliases = new HashSet<>();
        for (AdvertisingPlacement placement : settingsMap.keySet()) {
            int i = 0;
            long amountInpageAdvertising = settingsMap.get(placement).stream()
                    .map(AdvertisingSettings::getType)
                    .filter(ad -> ad == AdvertisingNetworkType.ADFOX_INPAGE)
                    .count();
            if (amountInpageAdvertising > 1 && throwOnError) {
                throw new WebmasterException("amount of inpage advertising must be < 2",
                        new SetTurboSettingsResponse.LotOfInPageAdvertisingInOnePlacementResponse(placement, platform));
            }
            for (AdvertisingSettings as : settingsMap.get(placement)) {
                as = as.withPlacement(placement);
                // проверка уникальности алиаса
                if (!Strings.isNullOrEmpty(as.getAlias())) {
                    if (aliases.contains(as.getAlias()) && throwOnError) {
                        throw new WebmasterException("Duplicate alias",
                                new SetTurboSettingsResponse.DuplicateAliasResponse(parsedAdSettings.size(), as, platform));
                    } else {
                        aliases.add(as.getAlias());
                    }
                }
                try {
                    parsedAdSettings.add(turboParserService.parseAdvertisingSettings(hostId, as, desktop));
                } catch (TurboParserException e) {
                    if (throwOnError) {
                        switch (e.getCode()) {
                            case YANDEX_AD_NOT_EXISTS:
                                throw new WebmasterException("Ad not exits",
                                        new SetTurboSettingsResponse.YandexAdNotExistsResponse(i, as, platform), e);
                            case YANDEX_AD_NON_TURBO:
                                throw new WebmasterException("Ad is not turbo",
                                        new SetTurboSettingsResponse.YandexAdNonTurboResponse(i, as, platform), e);
                            case ADFOX_INVALID_CODE:
                                throw new WebmasterException("Invalid adfox code",
                                        new SetTurboSettingsResponse.AdfoxInvalidCodeResponse(i, as, platform), e);
                            case YANDEX_INPAGE_INVALID_ID:
                                throw new WebmasterException("Invalid inpage id",
                                        new SetTurboSettingsResponse.YandexInpageInvalidIdResponse(i, as, platform), e);
                            case YANDEX_INSTREAM_INVALID_ID:
                                throw new WebmasterException("Invalid instream id",
                                        new SetTurboSettingsResponse.YandexInstreamInvalidIdResponse(i, as, platform), e);
                            case ADFOX_INPAGE_INVALID_CODE:
                                throw new WebmasterException("Invalid adfox inpage code",
                                        new SetTurboSettingsResponse.AdFoxInpageInvalidCodeResponse(i, as, platform), e);
                            case ADFOX_INSTREAM_INVALID_CODE:
                                throw new WebmasterException("Invalid adfox instream code",
                                        new SetTurboSettingsResponse.AdFoxInstreamInvalidCodeResponse(i, as, platform), e);
                            case ADFOX_INTERSCROLLER_INVALID_CODE:
                                throw new WebmasterException("Invalid adfox interscroller code",
                                        new SetTurboSettingsResponse.AdFoxInterscrollerInvalidCodeResponse(i, as, platform), e);
                            case YANDEX_REC_WIDGET_INVALID_ID:
                                throw new WebmasterException("Invalid rec_widget id",
                                        new SetTurboSettingsResponse.RecWidgetIdInvalidCodeResponse(i, as, platform), e);
                        }
                        throw new IllegalStateException("Unexpected code: " + e.getCode().name());
                    }
                }
                i++;
            }
        }
        return parsedAdSettings;
    }

    @NotNull
    private List<AnalyticsSettings> validateAnalyticsSettings(TurboHostSettings newSettings, boolean throwOnError) {
        // коды аналитики по типам (для проверки дубликатов)
        Map<AnalyticsSystemType, Set<String>> anCodesMap = new EnumMap<>(AnalyticsSystemType.class);
        List<AnalyticsSettings> parsedAnSettings = new ArrayList<>();

        //проверяем кол-во google счетчиков для которых разрешены расширенные и если > 1 значит что-то не так
        final long countExtendedGoogleSettings = newSettings.getAnalyticsSettings().stream()
                .filter(x -> x.getType() == AnalyticsSystemType.GOOGLE && ((AnalyticsSettings.GoogleAnalyticsSettings) x).getWithExtendedSettings())
                .count();
        if (countExtendedGoogleSettings > 1) {
            throw new WebmasterException("Invalid tracker",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(getClass(),
                            "countExtendedGoogleSettings",
                            Objects.toString(countExtendedGoogleSettings)
                    )
            );
        }

        for (AnalyticsSettings as : newSettings.getAnalyticsSettings()) {
            try {
                if (!anCodesMap.computeIfAbsent(as.getType(), type -> new HashSet<>()).add(as.getValue()) && throwOnError) {
                    throw new WebmasterException("Duplicate tracker",
                            new SetTurboSettingsResponse.DuplicateAnalyticsCodeResponse(parsedAnSettings.size(), as));
                }
                parsedAnSettings.add(turboParserService.parseAnalyticsSettings(as));
            } catch (TurboParserException e) {
                if (throwOnError) {
                    if (e.getCode() == TurboParserException.ErrorCode.INVALID_METRIKA_ID) {
                        throw new WebmasterException("Invalid tracker",
                                new SetTurboSettingsResponse.InvalidMetrikaIdResponse(parsedAnSettings.size(), as), e);
                    }
                    throw new WebmasterException("Invalid tracker",
                            new SetTurboSettingsResponse.InvalidAnalyticsCodeResponse(parsedAnSettings.size(), as), e);
                }
            }
        }
        return parsedAnSettings;
    }

    @NotNull
    private List<TurboMenuItem> validateMenuSettings(List<TurboMenuItem> menuItems, TurboHostSettingsBlock block, boolean throwOnError) {
        validateMenuSettings(menuItems, new ArrayList<>(), block, throwOnError);
        return menuItems;
    }

    private void validateMenuSettings(List<TurboMenuItem> menuItems, List<Integer> indexes, TurboHostSettingsBlock block,
                                      boolean throwOnError) {
        int maxItems;
        if (block == TurboHostSettingsBlock.MENU) {
            maxItems = TurboConstants.MAX_MENU_ITEMS;
        } else if (block == TurboHostSettingsBlock.TOP_MENU) {
            maxItems = TurboConstants.MAX_TOP_MENU_ITEMS;
        } else {
            // если не задано, то считаем, что нет предела
            maxItems = Integer.MAX_VALUE;
        }
        if (menuItems.size() > maxItems) {
            throw new WebmasterException("Too many menu items",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(getClass(), "menuSettings",
                            String.valueOf(menuItems.size())));
        }
        int maxDepth;
        if (block == TurboHostSettingsBlock.MENU) {
            maxDepth = TurboConstants.MAX_MENU_DEPTH;
        } else if (block == TurboHostSettingsBlock.TOP_MENU) {
            maxDepth = 1;
        } else {
            // если не задано, то считаем, что нет предела
            maxDepth = Integer.MAX_VALUE;
        }
        if (indexes.size() > maxDepth) {
            throw new WebmasterException("Too big menu depth",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(getClass(), "menuSettings",
                            String.valueOf(menuItems.size())));
        }
        for (int i = 0; i < menuItems.size(); i++) {
            List<Integer> newIndexes = new ArrayList<>(indexes);
            newIndexes.add(i);
            try {
                TurboMenuItem menuItem = menuItems.get(i);
                menuItems.set(i, TurboParserService.validateMenuItem(menuItem));
                if (!CollectionUtils.isEmpty(menuItem.getSubmenu())) {
                    validateMenuSettings(menuItem.getSubmenu(), newIndexes, block, throwOnError);
                }
            } catch (TurboParserException e) {
                if (throwOnError) {
                    switch (e.getCode()) {
                        case MENU_EMPTY_LABEL:
                            throw new WebmasterException("Invalid menu label", new SetTurboSettingsResponse.InvalidMenuLabelResponse(i, block, newIndexes), e);
                        case MENU_INVALID_URL:
                            throw new WebmasterException("Invalid menu url", new SetTurboSettingsResponse.InvalidMenuUrlResponse(i, block, newIndexes), e);
                        default:
                            throw new IllegalStateException("Unexpected code: " + e.getCode().name());
                    }
                }
            }
        }
    }

    @NotNull
    private TurboUserAgreement validateUserAgreement(TurboUserAgreement agreement, boolean throwOnError) {
        try {
            return turboParserService.validateUserAgreement(agreement);
        } catch (TurboParserException e) {
            if (!throwOnError) {
                return agreement;
            }
            switch (e.getCode()) {
                case INVALID_FEEDBACK_AGREEMENT:
                    throw new WebmasterException("Invalid user agreement", new SetTurboSettingsResponse.InvalidUserAgreementResponse(), e);
                case INVALID_FEEDBACK_AGREEMENT_URL:
                    throw new WebmasterException("Invalid agreement url", new SetTurboSettingsResponse.InvalidUserAgreementUrlResponse(), e);
            }
            throw new IllegalStateException("Unexpected code: " + e.getCode().name());
        }
    }

    private TurboFeedbackSettings validateFeedbackSettings(WebmasterHostId hostId, TurboFeedbackSettings settings, boolean throwOnError) {
        if (settings == null || settings.getButtons() == null) {
            return settings;
        }
        if (settings.getButtons().size() > TurboConstants.MAX_FEEDBACK_BUTTONS) {
            throw new WebmasterException("Too many feedback buttons", new WebmasterErrorResponse.IllegalParameterValueResponse(
                    TurboParserService.class, "settings", String.valueOf(settings.getButtons().size())));
        }
        // проверяем настройки блока обратной связи
        TurboFeedbackSettings validatedSettings = new TurboFeedbackSettings(settings.getStick(), new ArrayList<>());
        for (int i = 0; i < settings.getButtons().size(); i++) {
            try {
                validatedSettings.getButtons().add(TurboParserService.validateFeedbackButton(hostId, settings.getButtons().get(i), i));
            } catch (TurboParserException e) {
                if (throwOnError) {
                    switch (e.getCode()) {
                        case INVALID_FEEDBACK_URL:
                            throw new WebmasterException("Invalid feedback url", new SetTurboSettingsResponse.InvalidFeedbackUrlResponse(e.getIndex()), e);
                        case INVALID_FEEDBACK_EMAIL:
                            throw new WebmasterException("Invalid feedback email", new SetTurboSettingsResponse.InvalidFeedbackEmailResponse(e.getIndex()), e);
                        default:
                            throw new IllegalStateException("Unexpected code: " + e.getCode().name());
                    }
                }
            }
        }
        return validatedSettings;
    }

    private TurboCommerceSettings validateCommerceSettings(WebmasterHostId hostId, TurboCommerceSettings tcs, long userId,
                                                           Set<TurboHostSettingsBlock> blocks, boolean throwOnError) {
        TurboCommerceSettings commerceSettings;
        String domain = WwwUtil.cutWWWAndM(hostId);
        try {
            commerceSettings = turboParserService.validateCommerceSettings(hostId, tcs, userId, blocks);
            // при наличии хотя бы одного активного YML-фида необходимо выбрать метод заказа
            if (throwOnError && domainHasActiveYmlFeeds(domain) && !isOrderMethodSelected(hostId, commerceSettings)) {
                throw new WebmasterException("No order method selected", new SetTurboSettingsResponse.NoOrderMethodSelectedResponse());
            }
        } catch (TurboParserException e) {
            if (!throwOnError) {
                return null;
            }
            switch (e.getCode()) {
                case INVALID_CART_URL:
                    throw new WebmasterException("Invalid cart url", new SetTurboSettingsResponse.InvalidCartUrlResponse(), e);
                case DUPLICATE_COMMERCE_SECTION:
                    throw new WebmasterException("Duplicate commerce section", new SetTurboSettingsResponse.DuplicateCommerceSectionResponse(e.getIndex()), e);
                case INVALID_CHECKOUT_EMAIL:
                    throw new WebmasterException("Invalid checkout email", new SetTurboSettingsResponse.InvalidCheckoutEmailResponse(), e);
                case INVALID_TURBOCART_EMAIL:
                    throw new WebmasterException("Invalid turbo cart email", new SetTurboSettingsResponse.InvalidTurboCartEmailResponse(), e);
                case EMPTY_TURBOCART_EMAIL:
                    throw new WebmasterException("Empty turbo cart email", new SetTurboSettingsResponse.EmptyTurboCartEmailResponse(), e);
                case PAYMENTS_TOKEN_NOT_FOUND:
                    throw new WebmasterException("Payments token not found", new SetTurboSettingsResponse.PaymentsTokenNotFoundResponse(), e);
                case PAYMENTS_TOKEN_NOT_CONFIRMED:
                    throw new WebmasterException("Payments token not confirmed", new SetTurboSettingsResponse.PaymentsTokenNotConfirmedResponse(), e);
                case INVALID_BITRIX_URL:
                    throw new WebmasterException("Invalid bitrix api base url", new SetTurboSettingsResponse.InvalidBitrixApiBaseUrlResponse(), e);
                case INVALID_MIN_ORDER_VALUE:
                    throw new WebmasterException("Invalid min order value", new WebmasterErrorResponse.IllegalParameterValueResponse(
                            getClass(), "minOrderValue", String.valueOf(tcs.getMinOrderValue())), e);
            }
            throw new IllegalStateException();
        } catch (DeliverySettingsException exp) {
            if (!throwOnError) {
                return null;
            }
            throw new WebmasterException("Invalid turbo delivery settings parameters.",
                    new SetTurboSettingsResponse.IllegalParameterValueResponse(exp.getParameterName(),
                            exp.getParameterValue(),
                            exp.getMessage()), exp);
        }
        return commerceSettings;
    }

    private boolean isOrderMethodSelected(WebmasterHostId hostId, TurboCommerceSettings cs) {
        if (cs == null) {
            return false;
        }
        return cs.isOrderMethodSelected();
    }

    private boolean domainHasActiveYmlFeeds(String domain) {
        return turboFeedsService.getFeeds(domain).stream()
                .anyMatch(tfs -> tfs.getType() == TurboFeedType.YML && tfs.isActive());
    }


    private TurboSearchSettings validateSearchSettings(WebmasterHostId hostId, TurboSearchSettings searchSettings, boolean throwOnError) {
        if (searchSettings == null || Strings.isNullOrEmpty(searchSettings.getUrl())) {
            return searchSettings;
        }
        try {
            return TurboParserService.validateSearchSettings(hostId, searchSettings);
        } catch (TurboParserException e) {
            if (throwOnError) {
                throw new WebmasterException("Invalid search url", new SetTurboSettingsResponse.InvalidSearchUrlResponse(), e);
            }
            return searchSettings;
        }
    }

    private static TurboAuthorizationSettings validateAuthorizationSettings(TurboAuthorizationSettings settings, boolean throwOnError) {
        if (throwOnError && settings != null && !settings.isAllFieldsEmpty()) {
            String domain1 = validateUrl(settings.getAuthEndpoint(), SetTurboSettingsResponse.InvalidAuthEndpointUrlResponse::new);
            String domain2 = validateUrl(settings.getLoginEndpoint(), SetTurboSettingsResponse.InvalidLoginEndpointUrlResponse::new);
            String domain3 = validateUrl(settings.getLogoutEndpoint(), SetTurboSettingsResponse.InvalidLogoutEndpointUrlResponse::new);
            if (!domain1.equalsIgnoreCase(domain2) || !domain2.equalsIgnoreCase(domain3)) {
                throw new WebmasterException("Different domains in auth endpoints",
                        new SetTurboSettingsResponse.DifferentAuthEndpointDomainsResponse());
            }
            return settings;
        }
        return null;
    }

    private TurboCommentsSettings validateCommentsSettings(String domain, TurboCommentsSettings settings, boolean throwOnError) {
        if (settings == null) {
            return null;
        }
        if (throwOnError && abtService.isInHashExperiment(IdUtils.urlToHostId(domain), TURN_OFF_COMMENTATOR)
                && settings.getStatus() == TurboCommentsSettings.TURBO_COMMENT_STATUS.CMNT ) {
            throw new IllegalStateException("Turbo comments deprecated");
        }

        if (settings.getStatus() == TurboCommentsSettings.TURBO_COMMENT_STATUS.CMNT ||
                settings.getStatus() == TurboCommentsSettings.TURBO_COMMENT_STATUS.NO) {
            return settings;
        }
        if (Strings.isNullOrEmpty(settings.getAddCommentsEndpoint()) &&
                Strings.isNullOrEmpty(settings.getGetCommentsEndpoint())) {
            return null;
        }
        if (throwOnError) {
            String domain1 = validateUrl(settings.getAddCommentsEndpoint(), SetTurboSettingsResponse.InvalidAddCommentsEndpointUrlResponse::new);
            String domain2 = validateUrl(settings.getGetCommentsEndpoint(), SetTurboSettingsResponse.InvalidGetCommentsEndpointUrlResponse::new);
            if (!domain1.equalsIgnoreCase(domain2)) {
                throw new WebmasterException("Different domains in comments endpoints",
                        new SetTurboSettingsResponse.DifferentCommentsEndpointDomainsResponse());
            }
        }
        return settings;
    }

    private static String validateUrl(String url, Supplier<? extends SetTurboSettingsResponse.ErrorResponse> errorProducer) {
        try {
            URI uri = WebmasterUriUtils.toOldUri(url);
            if (!uri.getScheme().equals(WebmasterHostId.Schema.HTTPS.getSchemaName())) {
                throw new WebmasterException("Not https schema for url " + url, errorProducer.get());
            }
            return uri.getHost();
        } catch (UserException e) {
            throw new WebmasterException("Invalid url " + url, errorProducer.get());
        }
    }

    private static void validateColor(String color, String field) {
        if (!StringUtils.isBlank(color) && !COLOR_PATTERN.test(color)) {
            throw new WebmasterException("Invalid color value", new SetTurboSettingsResponse.InvalidColorResponse(field));
        }
    }

    private TurboDisplaySettings validateDisplaySettings(TurboDisplaySettings displaySettings, boolean throwOnError) {
        if (displaySettings == null || !throwOnError) {
            return displaySettings;
        }
        // проверим, что все цвета установлены (и светлая тема тоже)
        if (displaySettings.getLightMode() == null) {
            throw invalidParamValue("Missing light mode settings", "lightMode", "null");
        }
        // TODO
        return displaySettings;
    }

    private static WebmasterException invalidParamValue(String message, String param, String value) {
        return new WebmasterException(message,
                new WebmasterErrorResponse.IllegalParameterValueResponse(TurboSettingsMergerService.class, param, value));
    }

}
