package ru.yandex.webmaster3.viewer.http.turbo.sandbox;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Preconditions;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.turbo.TurboConstants;
import ru.yandex.webmaster3.core.turbo.ValidateTurboFeedTaskData;
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.TurboUrl;
import ru.yandex.webmaster3.core.turbo.model.app.TurboAppSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboNewCommerceSettings;
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.error.TurboErrorType;
import ru.yandex.webmaster3.core.turbo.model.error.TurboRawError;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboErrorInfo;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedSettings;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedState;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedType;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.WwwUtil;
import ru.yandex.webmaster3.core.worker.client.WorkerClient;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.model.Experiment;
import ru.yandex.webmaster3.storage.turbo.dao.TurboDomainsStateCHDao;
import ru.yandex.webmaster3.storage.turbo.dao.TurboDomainsStateHelper;
import ru.yandex.webmaster3.storage.turbo.dao.TurboFeedsSamplesYDao;
import ru.yandex.webmaster3.storage.turbo.dao.automorda.TurboAutoMordaStatus;
import ru.yandex.webmaster3.storage.turbo.service.TurboDomainsStateService.TurboDomainState;
import ru.yandex.webmaster3.storage.turbo.service.TurboFeedRawStatsData;
import ru.yandex.webmaster3.storage.turbo.service.TurboFeedsService;
import ru.yandex.webmaster3.storage.turbo.service.css.TurboCssValidatorResponse;
import ru.yandex.webmaster3.storage.turbo.service.preview.TurboFeedPreviewService;
import ru.yandex.webmaster3.storage.turbo.service.settings.TurboSettingsMergerService;
import ru.yandex.webmaster3.storage.turbo.service.settings.TurboSettingsService;
import ru.yandex.webmaster3.storage.user.UserPersonalInfo;
import ru.yandex.webmaster3.storage.user.service.UserPersonalInfoService;
import ru.yandex.webmaster3.tanker.I18nTurboSandboxExample;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;

import static ru.yandex.webmaster3.core.turbo.TurboConstants.PREVIEW_SHOP_URL_SUFFIX;
import static ru.yandex.webmaster3.core.turbo.TurboConstants.PREVIEW_URL_SUFFIX;
import static ru.yandex.webmaster3.core.turbo.TurboConstants.TURBO_BASE_URL;
import static ru.yandex.webmaster3.core.turbo.TurboConstants.TURBO_LISTINGS_SUFFIX;

/**
 * Created by Oleg Bazdyrev on 17/04/2018.
 */
@ReadAction
@Category("turbo")
@Description("Создание превью турбо-страницы по тексту")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class CreateTurboPreviewAction extends
        AbstractUserVerifiedHostAction<CreateTurboPreviewRequest, CreateTurboPreviewResponse> {

    private static final Logger log = LoggerFactory.getLogger(CreateTurboPreviewAction.class);
    private static final long SANDBOX_CONTENT_TTL = TimeUnit.MINUTES.toSeconds(15L);
    private static final Duration FEED_SAMPLE_MAX_AGE = Duration.standardHours(1L);
    private static final String DEFAULT_AUTOMORDA_STUB = "https://yandex.ru/turbo?text=auto_main";

    private final AbtService abtService;
    private final TurboDomainsStateCHDao turboDomainsStateCHDao;
    private final TurboFeedPreviewService turboFeedPreviewService;
    private final TurboFeedsSamplesYDao turboFeedsSamplesYDao;
    private final TurboFeedsService turboFeedsService;
    private final TurboSettingsMergerService turboSettingsMergerService;
    private final TurboSettingsService turboSettingsService;
    private final UserPersonalInfoService userPersonalInfoService;
    private final WorkerClient workerClient;

    @Override
    public CreateTurboPreviewResponse process(CreateTurboPreviewRequest request) {
        // generate document
        Preconditions.checkState(request.getType() == TurboFeedType.RSS || request.getType() == TurboFeedType.YML);
        WebmasterHostId hostId = request.getHostId();
        String domain = WwwUtil.cutWWWAndM(hostId);
        Set<TurboHostSettingsBlock> settingsBlocks = request.getSettingsBlocks();
        Set<TurboDesktopSettingsBlock> desktopSettingsBlocks = request.getDesktopSettingsBlocks();
        if (settingsBlocks.contains(TurboHostSettingsBlock.AUTO_MORDA) &&
                request.getType() == TurboFeedType.RSS) {
            return createAutoMordaResponse(hostId);
        }
        if (settingsBlocks.contains(TurboHostSettingsBlock.TURBO_LISTINGS) &&
                request.getType() == TurboFeedType.YML) {
            return createTurboListingsResponse(hostId);
        }
        String link = IdUtils.hostIdToUrl(hostId) + TurboConstants.SANDBOX_PREVIEW_PATH;
        MutableInt errorLineCorrection = new MutableInt();
        String content = request.getContent();
        if (content == null) {
            // попытаемся найти эакэшированный
            try {
                Pair<String, DateTime> pair = turboFeedsSamplesYDao.getContentAndUpdateDate(domain, request.getType());
                if (pair == null || pair.getRight() == null || pair.getRight().isBefore(DateTime.now().minus(FEED_SAMPLE_MAX_AGE))) {
                    // если данные протухли - обновим их в фоне
                    refreshFeedSample(hostId, request.getType());
                }
                content = pair != null ? pair.getLeft() : null;
            } catch (WebmasterYdbException e) {
                throw e.asUnchecked("Could not load feed sample from Cassandra", getClass());
            }
        }

        if (content == null) {
            content = getDefaultContent(request);
        }

        byte[] feed = request.getType() == TurboFeedType.RSS ?
                TurboFeedPreviewService.createTurboRss(content, hostId, errorLineCorrection, request.getUrlSuffix()) :
                TurboFeedPreviewService.createTurboYml(content, hostId, errorLineCorrection, request.getUrlSuffix());

        if (feed == null) {
            return new CreateTurboPreviewResponse.InvalidRootTagResponse();
        }

        MutableObject<TurboCssValidatorResponse> cssValidateResponse = new MutableObject<>();
        MutableObject<TurboCssValidatorResponse> desktopCssValidateResponse = new MutableObject<>();
        TurboHostSettings newSettings = request.getSettings();
        if (settingsBlocks.isEmpty() && request.getSettings() == null) {
            newSettings = new TurboHostSettingsBuilder().setCss(request.getCss(), request.getCss()).build();
            settingsBlocks = EnumSet.of(TurboHostSettingsBlock.CSS);
        }
        TurboHostSettingsBuilder settingsBuilder = turboSettingsMergerService.mergeHostSettings(hostId, request.getUserId(),
                newSettings, settingsBlocks, false, cssValidateResponse);
        TurboDesktopSettingsBuilder desktopSettingsBuilder = turboSettingsMergerService.mergeDesktopSettings(hostId, request.getUserId(),
                request.getDesktopSettings(), desktopSettingsBlocks, false, desktopCssValidateResponse);
        TurboNewCommerceSettings.TurboNewCommerceSettingsBuilder commerceSettingsBuilder = turboSettingsMergerService.mergeCommerceSettings(
                hostId, request.getCommerceSettings(), request.getCommerceSettingBlocks(), false, settingsBuilder.build());
        // TODO
        TurboAppSettings.TurboAppSettingsBuilder appSettingsBuilder = TurboAppSettings.builder();
        turboSettingsService.splitSettings(commerceSettingsBuilder.build(), appSettingsBuilder, settingsBuilder);

        TurboFeedRawStatsData data = turboFeedPreviewService.uploadFeed(hostId, link, request.getType(), feed,
                settingsBuilder.build(), desktopSettingsBuilder.build(), appSettingsBuilder.build(), SANDBOX_CONTENT_TTL);
        // 2 строка в результирующем xml будет отдана под <?xml version="1.0" encoding="UTF-8"?> и channel
        // плюс startLineNumber начинается с 1
        // новый режим
        List<TurboErrorInfo> errors = new ArrayList<>();
        List<TurboErrorInfo> warnings = new ArrayList<>();
        for (TurboRawError rawError : data.collectTurboRawErrors()) {
            TurboErrorType type = TurboErrorType.fromRawError(rawError);
            if (type == null || type == TurboErrorType.UNKNOWN) {
                continue; // игнорируемая ошибка
            }
            TurboErrorInfo errorInfo = TurboErrorInfo.create(type,
                    rawError.getLine() == null ? null : (rawError.getLine() + errorLineCorrection.intValue()),
                    rawError.getColumn(), rawError.getText(), rawError.getParams());
            if (rawError.isFatal().orElse(type.isFatal())) {
                errors.add(errorInfo);
            } else {
                warnings.add(errorInfo);
            }
        }
        String previewUrl = data.collectTurboUrls("", false).stream().map(TurboUrl::getTurboUrl)
                .filter(Objects::nonNull).findAny()
                .map(url -> url + PREVIEW_URL_SUFFIX + new Random().nextLong())
                .map(url -> abtService.isInExperiment(request.getUserId(), Experiment.TURBO_ABOUT_SHOP) ? url + PREVIEW_SHOP_URL_SUFFIX : url)
                .map(url -> request.getType() == TurboFeedType.YML ? (url + "&preview=ecom") : url)
                .orElse(null);

        // WMC-7793 Если контента в запросе нет (т.е. НЕ режим отладки), но есть ошибки (нет превью), то откатываемся на дефолтный rss/yml
        if (request.getContent() == null && previewUrl == null) {
            // дефолтный текст из танкера
            request.setContent(getDefaultContent(request));
            return process(request);
        }

        return new CreateTurboPreviewResponse.NormalResponse(previewUrl, warnings, errors,
                cssValidateResponse.getValue() == null ? null : cssValidateResponse.getValue().createErrorInfos(),
                desktopCssValidateResponse.getValue() == null ? null : desktopCssValidateResponse.getValue().createErrorInfos());
    }

    private String getDefaultContent(CreateTurboPreviewRequest request) {
        String link = IdUtils.hostIdToUrl(request.getHostId()) + TurboConstants.SANDBOX_PREVIEW_PATH;
        UserPersonalInfo userInfo = userPersonalInfoService.getUsersPersonalInfos(
                Collections.singleton(request.getUserId())).get(request.getUserId());
        if (request.getType() == TurboFeedType.RSS) {
            return I18nTurboSandboxExample.TURBO_ITEM.newBuilder(userInfo.getLanguage()).url(link).render();
        } else {
            return I18nTurboSandboxExample.TURBO_YML.newBuilder(userInfo.getLanguage()).url(link).render();
        }
    }

    private CreateTurboPreviewResponse createTurboListingsResponse(WebmasterHostId hostId) {
        String url = TURBO_BASE_URL + WwwUtil.cutWWWAndM(hostId) + TURBO_LISTINGS_SUFFIX + PREVIEW_URL_SUFFIX;
        return new CreateTurboPreviewResponse.NormalResponse(url + new Random().nextLong(), null, null, null, null);
    }

    private CreateTurboPreviewResponse createAutoMordaResponse(WebmasterHostId hostId) {
        // пошаримся в примерах, поищем наш хост

        // только поле domain и automordaStatus automordaSample
        TurboDomainState domainState = turboDomainsStateCHDao.getDomainState(WwwUtil.cutWWWAndM(hostId), TurboDomainsStateHelper.getAutomordaFields());
        String sampleUrl;
        if (domainState.getAutoMordaStatus() == TurboAutoMordaStatus.OK) {
            String domain = WwwUtil.cutWWWAndM(hostId);
            sampleUrl = domainState.getAutomordaSamples().stream()
                    .filter(s -> WwwUtil.cutWWWAndM(IdUtils.urlToHostId(s.getOriginUrl())).equals(domain)).findAny()
                    .orElse(getRandomItem(domainState.getAutomordaSamples())).getTurboUrl();
        } else {
            sampleUrl = DEFAULT_AUTOMORDA_STUB;
        }
        return new CreateTurboPreviewResponse.NormalResponse(sampleUrl + PREVIEW_URL_SUFFIX + new Random().nextLong(),
                null, null, null, null);
    }

    private static <T> T getRandomItem(List<T> list) {
        return list.get(new Random().nextInt(list.size()));
    }

    private void refreshFeedSample(WebmasterHostId hostId, TurboFeedType type) {
        String domain = WwwUtil.cutWWWAndM(hostId);
        Optional<TurboFeedSettings> feed = turboFeedsService.getFeeds(domain).stream()
                .filter(f -> f.getState() != TurboFeedState.DELETED)
                .filter(f -> f.getType() == type).findAny();
        if (feed.isPresent()) {
            turboFeedsSamplesYDao.refreshUpdateDate(domain, type);
            workerClient.enqueueTask(new ValidateTurboFeedTaskData(hostId, feed.get().getUrl(), false, type));
        }
    }

}
