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

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.lang3.StringUtils;
import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.turbo.TurboConstants;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * Пример ошибки
 */
public final class TurboRawError {

    private static final ObjectMapper OM = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    public static final TypeReference<List<TurboRawError>> TYPE_REFERENCE =
            new TypeReference<List<TurboRawError>>() {
            };
    public static final Comparator<TurboRawError> BY_CODE_AND_LINE = Comparator.comparing(TurboRawError::getCode)
            .thenComparing(TurboRawError::getLine, ComparatorUtils.nullLowComparator(null));
    private static final int MAX_SAMPLE_LENGTH = 1000;
    private static final String DETAILS_FIELD = "details";
    private static final String CODE_FIELD = "code";
    private static final String SEVERITY_FIELD = "severity";
    private static final String LINE_FIELD = "line";
    private static final String COLUMN_FIELD = "column";
    private static final String CONTEXT_FIELD = "context";
    private static final String MESSAGE_FIELD = "message";
    private static final String ITEM_URL_FIELD = "itemUrl";
    private static final List<String> ALL_KNOWN_FIELDS =
            Lists.newArrayList(CODE_FIELD, LINE_FIELD, COLUMN_FIELD, CONTEXT_FIELD);


    private final String code;
    private final Integer line;
    private final Integer column;
    private final String text;
    private final TurboSeverity severity;
    private final ObjectNode params;

    public TurboRawError(String code, Integer line, Integer column, String text, TurboSeverity severity,
                         ObjectNode params) {
        this.code = code;
        this.line = line;
        this.column = column;
        this.text = text;
        this.severity = severity;
        this.params = params;
    }

    @Description("Исходный код ошибки от турбо")
    @JsonProperty("code")
    public String getCode() {
        return code;
    }

    @Description("Номер строки с ошибкой")
    @JsonProperty("lineNumber")
    public Integer getLine() {
        return line;
    }

    @Description("Номер столбца с ошибкой")
    @JsonProperty("posNumber")
    public Integer getColumn() {
        return column;
    }

    @Description("Строка с ошибкой")
    @JsonProperty("message")
    public String getText() {
        return text;
    }

    @Description("Важность ошибки - ошибка/предупреждение")
    @JsonProperty("severity")
    public TurboSeverity getSeverity() {
        return severity;
    }

    @JsonIgnore
    public Optional<Boolean> isFatal() {
        return Optional.ofNullable(severity).map(TurboSeverity::isFatal);
    }

    @Description("Дополнительные параметры ошибки")
    public ObjectNode getParams() {
        return params;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TurboRawError rawError = (TurboRawError) o;
        return Objects.equals(code, rawError.code) &&
                Objects.equals(line, rawError.line) &&
                Objects.equals(column, rawError.column) &&
                Objects.equals(text, rawError.text) &&
                severity == rawError.severity &&
                Objects.equals(params, rawError.params);
    }

    @Override
    public int hashCode() {
        return Objects.hash(code);
    }

    public static TurboRawError fromObjectNode(ObjectNode error, String itemUrl) {
        JsonNode details = error.get(DETAILS_FIELD);
        String code = error.get(CODE_FIELD).asText();
        TurboSeverity severity = Optional.ofNullable(error.get(SEVERITY_FIELD)).map(JsonNode::asText)
                .map(String::toUpperCase).map(TurboSeverity::valueOf).orElse(null);
        String message = Optional.ofNullable(error.get(MESSAGE_FIELD)).map(JsonNode::asText).orElse(null);
        if (details == null || details.getNodeType() != JsonNodeType.OBJECT) {
            ObjectNode params = itemUrl == null ? null : OM.createObjectNode()
                    .put(ITEM_URL_FIELD, itemUrl).put(MESSAGE_FIELD, message);
            // хотя бы сохраним url item-а
            return new TurboRawError(code, null, null, null, severity, params);
        }

        Integer line = Optional.ofNullable(details.get(LINE_FIELD)).map(JsonNode::asInt).orElse(null);
        Integer column = Optional.ofNullable(details.get(COLUMN_FIELD)).map(JsonNode::asInt).orElse(null);
        String context = Optional.ofNullable(details.get(CONTEXT_FIELD)).map(JsonNode::asText).orElse(null);
        // всё остальное
        ObjectNode params = ((ObjectNode) details.deepCopy()).remove(ALL_KNOWN_FIELDS);
        if (!Strings.isNullOrEmpty(itemUrl) && !params.has(TurboConstants.FIELD_ITEM_URL)) {
            params.put(TurboConstants.FIELD_ITEM_URL, itemUrl);
        }
        if (message != null && !params.has(MESSAGE_FIELD)) {
            params.put(MESSAGE_FIELD, message);
        }
        return new TurboRawError(code, line, column, StringUtils.abbreviate(context, MAX_SAMPLE_LENGTH), severity, params);
    }
}
