package ru.yandex.webmaster3.core.turbo.model.feed;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Strings;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import ru.yandex.autodoc.common.doc.annotation.Description;
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.statistics.TurboTotalStatistics;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Составная информация о турбо-источнике для фронтенда
 * Created by Oleg Bazdyrev on 07/08/2017.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public class TurboFeedInfo {
    public static final Comparator<TurboFeedInfo> BY_ADD_DATE = Comparator.
            comparing(f -> f.addDate == null ? 0L : f.addDate.getMillis(), Comparator.reverseOrder());

    private final String url;
    private final UUID taskId;
    private final TurboFeedType type;
    private final boolean active;
    private final TurboFeedInfoState state;
    private final DateTime addDate;
    private final DateTime downloadDate;
    private final DateTime updateDate;
    private final List<TurboErrorInfo> warnings;
    private final List<TurboErrorInfo> errors;
    private final TurboFeedItemStatistics stats;
    private final TurboTotalStatistics totalStats;
    private final String hash;
    private final boolean hasPreview;

    @JsonCreator
    public TurboFeedInfo(@JsonProperty("url") String url,
                         @JsonProperty("taskId") UUID taskId,
                         @JsonProperty("type") TurboFeedType type,
                         @JsonProperty("active") boolean active,
                         @JsonProperty("state") TurboFeedInfoState state,
                         @JsonProperty("addDate") DateTime addDate,
                         @JsonProperty("downloadDate") DateTime downloadDate,
                         @JsonProperty("updateDate") DateTime updateDate,
                         @JsonProperty("warnings") List<TurboErrorInfo> warnings,
                         @JsonProperty("errors") List<TurboErrorInfo> errors,
                         @JsonProperty("stats") TurboFeedItemStatistics stats,
                         @JsonProperty("totalStats") TurboTotalStatistics totalStats,
                         @JsonProperty("hash") String hash,
                         @JsonProperty("hasPreview") boolean hasPreview) {
        this.url = url;
        this.taskId = taskId;
        this.type = type;
        this.active = active;
        this.state = state;
        this.addDate = addDate;
        this.downloadDate = downloadDate;
        this.updateDate = updateDate;
        this.warnings = warnings;
        this.errors = errors;
        this.stats = stats;
        this.totalStats = totalStats;
        this.hash = hash;
        this.hasPreview = hasPreview;
    }

    public TurboFeedInfo(@NotNull TurboFeedSettingsInfo settings, TurboFeedStatisticsInfo statistics,
                         TurboTotalStatistics.TurboTotalStatisticsBuilder totalStatsBuilder) {
        this.url = settings.getUrl();
        this.taskId = settings.getTaskId();
        this.type = settings.getType();
        this.active = settings.isActive();
        this.addDate = settings.getAddDate();
        if (statistics == null) {
            this.downloadDate = null;
            this.updateDate = null;
            this.warnings = new ArrayList<>();
            this.errors = new ArrayList<>();
            this.stats = null;
            this.hash = null;
            this.hasPreview = false;
        } else {
            this.stats = statistics.getStats();
            this.downloadDate = statistics.getDownloadDate();
            this.updateDate = statistics.getUpdateDate();
            Pair<List<TurboErrorInfo>, List<TurboErrorInfo>> pair = processErrors(statistics);
            this.warnings = pair.getLeft();
            this.errors = pair.getRight();
            this.hash = statistics.getHash();
            this.hasPreview = statistics.getTurboUrls() != null && statistics.getTurboUrls().stream()
                    .anyMatch(turboUrl -> !Strings.isNullOrEmpty(turboUrl.getTurboUrl()));
        }
        this.state = convertState(settings, statistics);
        this.totalStats = totalStatsBuilder == null ? TurboTotalStatistics.EMPTY : totalStatsBuilder.build();
    }

    public static Pair<List<TurboErrorInfo>, List<TurboErrorInfo>> processErrors(TurboFeedStatisticsInfo statistics) {
        List<TurboErrorInfo> errors, warnings;
        if (statistics.getRawErrors() == null) {
            // старый режим для совместимости
            warnings = statistics.getErrors().stream().filter(e -> e.getType() != null)
                    .filter(e -> !e.getType().isFatal()).flatMap(TurboErrorInfo::fromTurboError)
                    .collect(Collectors.toList());
            errors = statistics.getErrors().stream().filter(e -> e.getType() != null)
                    .filter(e -> e.getType().isFatal()).flatMap(TurboErrorInfo::fromTurboError)
                    .collect(Collectors.toList());
        } else {
            // новый режим
            errors = new ArrayList<>();
            warnings = new ArrayList<>();
            for (TurboRawError rawError : statistics.getRawErrors()) {
                TurboErrorType type = TurboErrorType.fromRawError(rawError);
                if (type == null || type == TurboErrorType.UNKNOWN) {
                    continue; // игнорируемая ошибка
                }
                if (rawError.isFatal().orElse(type.isFatal())) {
                    errors.add(TurboErrorInfo.fromRawError(type, rawError));
                } else {
                    warnings.add(TurboErrorInfo.fromRawError(type, rawError));
                }
            }
        }
        return Pair.of(warnings, errors);
    }

    private TurboFeedInfoState convertState(TurboFeedSettingsInfo settings, TurboFeedStatisticsInfo statistics) {
        boolean active = settings.isActive();
        if (settings.isValidating()) {
            return TurboFeedInfoState.PROCESSING;
        }
        if (statistics == null) {
            if (active) {
                return TurboFeedInfoState.PROCESSING;
            } else {
                // фид по какой-то странной причине еще ни разу не обработался
                errors.add(new TurboErrorInfo(TurboErrorType.INTERNAL, null, null, null, null));
                return TurboFeedInfoState.VALIDATION_ERROR;
            }
        }
        switch (statistics.getState()) {
            case OK:
                return TurboFeedInfoState.READY;
            case WARNING:
                return active ? TurboFeedInfoState.UPDATE_WARNING : TurboFeedInfoState.VALIDATION_WARNING;
            case ERROR:
                return active ? TurboFeedInfoState.UPDATE_ERROR : TurboFeedInfoState.VALIDATION_ERROR;
        }
        throw new IllegalStateException();
    }

    @Description("Ссылка на фид. Для API - null")
    public String getUrl() {
        return url;
    }

    @Description("Идентификатор фида API, null для RSS")
    public UUID getTaskId() {
        return taskId;
    }

    @Description("Тип фида")
    public TurboFeedType getType() {
        return type;
    }

    @Description("Активен ли фид (есть ли в поисковой выдаче)")
    public boolean isActive() {
        return active;
    }

    @Description("Можно ли активировать фид")
    public boolean isCanActivate() {
        return type == TurboFeedType.RSS || type == TurboFeedType.YML;
    }

    @Description("Состояние фида")
    public TurboFeedInfoState getState() {
        return state;
    }

    @Description("Дата добавления фида пользователем")
    public DateTime getAddDate() {
        return addDate;
    }

    @Description("Дата последней загрузки фида роботом (или валидации, если еще не загружался)")
    public DateTime getDownloadDate() {
        return downloadDate;
    }

    @Description("Дата последней прокачки фида в турбо")
    public DateTime getUpdateDate() {
        return updateDate;
    }

    @Description("Предупреждения с примерами")
    public List<TurboErrorInfo> getWarnings() {
        return warnings;
    }

    @Description("Ошибки с примерами")
    public List<TurboErrorInfo> getErrors() {
        return errors;
    }

    @Description("Статистика по последней прокачке фида")
    public TurboFeedItemStatistics getStats() {
        return stats;
    }

    @Description("Общая статистика по фиду")
    public TurboTotalStatistics getTotalStats() {
        return totalStats;
    }

    @Description("Отладочная информация")
    public String getHash() {
        return hash;
    }

    @Description("Есть ли страницы с превью у данного фида")
    public boolean isHasPreview() {
        return hasPreview;
    }

    public TurboFeedInfo withTotalStats(TurboTotalStatistics totalStats) {
        return new TurboFeedInfo(url, taskId, type, active, state, addDate, downloadDate, updateDate, warnings,
                errors, stats, totalStats, hash, hasPreview);
    }

    public enum TurboFeedInfoState {
        @Description("Источник обратывается в данный момент (валидируется или загружается)")
        PROCESSING,
        @Description("Ошибка валидации источника")
        VALIDATION_ERROR,
        @Description("Есть предупреждения при валидации источника")
        VALIDATION_WARNING,
        @Description("Ошибка обновления источника")
        UPDATE_ERROR,
        @Description("Есть предупреждения при обновлении источника")
        UPDATE_WARNING,
        @Description("Источник проверен и готов к активации")
        READY,
    }
}
