package ru.yandex.webmaster3.internal.turbo;

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.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Value;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.ActionResponse;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.http.RequestQueryProperty;
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.app.TurboAppSettings.TurboAppSettingsBuilder;
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.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.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.internal.common.InternalAction;
import ru.yandex.webmaster3.internal.common.request.AbstractUserVerifiedHostRequest;
import ru.yandex.webmaster3.internal.common.security.ActionInternalGrant;
import ru.yandex.webmaster3.internal.common.security.InternalGrant;
import ru.yandex.webmaster3.storage.turbo.dao.TurboFeedsSamplesYDao;
import ru.yandex.webmaster3.storage.turbo.service.TurboFeedRawStatsData;
import ru.yandex.webmaster3.storage.turbo.service.TurboFeedsService;
import ru.yandex.webmaster3.storage.turbo.service.css.TurboCssErrorInfo;
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 static ru.yandex.webmaster3.core.turbo.TurboConstants.PREVIEW_URL_SUFFIX;

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

    private static final long SANDBOX_CONTENT_TTL = TimeUnit.MINUTES.toSeconds(15L);
    private static final Duration FEED_SAMPLE_MAX_AGE = Duration.standardHours(1L);

    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 Response process(Request request) {
        // generate document
        Preconditions.checkState(request.getType() == TurboFeedType.RSS || request.getType() == TurboFeedType.YML);
        WebmasterHostId hostId = request.getHostId();
        String domain = WwwUtil.cutWWWAndM(hostId);
        String link = IdUtils.hostIdToUrl(hostId) + TurboConstants.SANDBOX_PREVIEW_PATH;
        MutableInt errorLineCorrection = new MutableInt();
        // попытаемся найти эакэшированный
        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());
        }
        String content = pair != null ? pair.getLeft() : null;
        if (content == null) {
            content = getDefaultContent(request);
        }

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

        MutableObject<TurboCssValidatorResponse> cssValidateResponse = new MutableObject<>();
        MutableObject<TurboCssValidatorResponse> desktopCssValidateResponse = new MutableObject<>();
        TurboHostSettingsBuilder settingsBuilder = turboSettingsMergerService.mergeHostSettings(hostId, request.getUserId(),
                request.getSettings(), request.getSettingsBlocks(), false, cssValidateResponse);
        TurboDesktopSettingsBuilder desktopSettingsBuilder = turboSettingsMergerService.mergeDesktopSettings(hostId, request.getUserId(),
                request.getDesktopSettings(), request.getDesktopSettingsBlocks(), false, desktopCssValidateResponse);
        TurboNewCommerceSettingsBuilder commerceSettingsBuilder = turboSettingsMergerService.mergeCommerceSettings(
                hostId, request.getCommerceSettings(), request.getCommerceSettingBlocks(), false, settingsBuilder.build());
        // TODO
        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())
                .orElse(null);

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

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

    private String getDefaultContent(Request 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 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));
        }
    }

    @Getter
    @Setter(onMethod_ = @RequestQueryProperty)
    public static final class Request extends AbstractUserVerifiedHostRequest {
        @Description("Контент для превью (необязательно)")
        private String content;
        @Description("Похостовые настройки")
        private TurboHostSettings settings;
        @Description("Настройки Турбо для десктопа")
        private TurboDesktopSettings desktopSettings;
        @Description("Настройки Турбо для нового е-коммерс")
        private TurboNewCommerceSettings commerceSettings;
        private Set<TurboHostSettingsBlock> settingsBlocks = EnumSet.noneOf(TurboHostSettingsBlock.class);
        private Set<TurboDesktopSettingsBlock> desktopSettingsBlocks = EnumSet.noneOf(TurboDesktopSettingsBlock.class);
        private Set<TurboCommerceSettingsBlock> commerceSettingBlocks = EnumSet.noneOf(TurboCommerceSettingsBlock.class);
        private TurboFeedType type = TurboFeedType.RSS;
    }

    @Value
    public static final class Response implements ActionResponse.NormalResponse {
        @Description("Ссылка на превью")
        String previewUrl;
        @Description("Предупреждения при парсинге")
        List<TurboErrorInfo> warnings;
        @Description("Ошибки при парсинге")
        List<TurboErrorInfo> errors;
        @Description("Ошибки css")
        List<TurboCssErrorInfo> cssErrors;
        @Description("Ошибки десктопного css")
        List<TurboCssErrorInfo> desktopCssErrors;
    }

}
