package com.termux.shared.errors;

import android.content.Context;
import androidx.annotation.NonNull;
import com.termux.shared.logger.Logger;
import com.termux.shared.markdown.MarkdownUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Error implements Serializable {

    /**
     * The optional error label.
     */
    private String label;

    /**
     * The error type.
     */
    private String type;

    /**
     * The error code.
     */
    private int code;

    /**
     * The error message.
     */
    private String message;

    /**
     * The error exceptions.
     */
    private List<Throwable> throwablesList = new ArrayList<>();

    private static final String LOG_TAG = "Error";

    public Error() {
        InitError(null, null, null, null);
    }

    public Error(String type, Integer code, String message, List<Throwable> throwablesList) {
        InitError(type, code, message, throwablesList);
    }

    public Error(String type, Integer code, String message, Throwable throwable) {
        InitError(type, code, message, Collections.singletonList(throwable));
    }

    public Error(String type, Integer code, String message) {
        InitError(type, code, message, null);
    }

    public Error(Integer code, String message, List<Throwable> throwablesList) {
        InitError(null, code, message, throwablesList);
    }

    public Error(Integer code, String message, Throwable throwable) {
        InitError(null, code, message, Collections.singletonList(throwable));
    }

    public Error(Integer code, String message) {
        InitError(null, code, message, null);
    }

    public Error(String message, Throwable throwable) {
        InitError(null, null, message, Collections.singletonList(throwable));
    }

    public Error(String message, List<Throwable> throwablesList) {
        InitError(null, null, message, throwablesList);
    }

    public Error(String message) {
        InitError(null, null, message, null);
    }

    private void InitError(String type, Integer code, String message, List<Throwable> throwablesList) {
        if (type != null && !type.isEmpty())
            this.type = type;
        else
            this.type = Errno.TYPE;
        if (code != null && code > Errno.ERRNO_SUCCESS.getCode())
            this.code = code;
        else
            this.code = Errno.ERRNO_SUCCESS.getCode();
        this.message = message;
        if (throwablesList != null)
            this.throwablesList = throwablesList;
    }

    public Error setLabel(String label) {
        this.label = label;
        return this;
    }

    public String getLabel() {
        return label;
    }

    public String getType() {
        return type;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public void prependMessage(String message) {
        if (message != null && isStateFailed())
            this.message = message + this.message;
    }

    public void appendMessage(String message) {
        if (message != null && isStateFailed())
            this.message = this.message + message;
    }

    public List<Throwable> getThrowablesList() {
        return Collections.unmodifiableList(throwablesList);
    }

    public synchronized boolean setStateFailed(@NonNull Error error) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), null);
    }

    public synchronized boolean setStateFailed(@NonNull Error error, Throwable throwable) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), Collections.singletonList(throwable));
    }

    public synchronized boolean setStateFailed(@NonNull Error error, List<Throwable> throwablesList) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), throwablesList);
    }

    public synchronized boolean setStateFailed(int code, String message) {
        return setStateFailed(this.type, code, message, null);
    }

    public synchronized boolean setStateFailed(int code, String message, Throwable throwable) {
        return setStateFailed(this.type, code, message, Collections.singletonList(throwable));
    }

    public synchronized boolean setStateFailed(int code, String message, List<Throwable> throwablesList) {
        return setStateFailed(this.type, code, message, throwablesList);
    }

    public synchronized boolean setStateFailed(String type, int code, String message, List<Throwable> throwablesList) {
        this.message = message;
        this.throwablesList = throwablesList;
        if (type != null && !type.isEmpty())
            this.type = type;
        if (code > Errno.ERRNO_SUCCESS.getCode()) {
            this.code = code;
            return true;
        } else {
            Logger.logWarn(LOG_TAG, "Ignoring invalid error code value \"" + code + "\". Force setting it to RESULT_CODE_FAILED \"" + Errno.ERRNO_FAILED.getCode() + "\"");
            this.code = Errno.ERRNO_FAILED.getCode();
            return false;
        }
    }

    public boolean isStateFailed() {
        return code > Errno.ERRNO_SUCCESS.getCode();
    }

    @NonNull
    @Override
    public String toString() {
        return getErrorLogString(this);
    }

    /**
     * Log the {@link Error} and show a toast for the minimal {@link String} for the {@link Error}.
     *
     * @param context The {@link Context} for operations.
     * @param logTag The log tag to use for logging.
     * @param error The {@link Error} to convert.
     */
    public static void logErrorAndShowToast(Context context, String logTag, Error error) {
        if (error == null)
            return;
        error.logErrorAndShowToast(context, logTag);
    }

    public void logErrorAndShowToast(Context context, String logTag) {
        Logger.logErrorExtended(logTag, getErrorLogString());
        Logger.showToast(context, getMinimalErrorLogString(), true);
    }

    /**
     * Get a log friendly {@link String} for {@link Error} error parameters.
     *
     * @param error The {@link Error} to convert.
     * @return Returns the log friendly {@link String}.
     */
    public static String getErrorLogString(final Error error) {
        if (error == null)
            return "null";
        return error.getErrorLogString();
    }

    public String getErrorLogString() {
        StringBuilder logString = new StringBuilder();
        logString.append(getCodeString());
        logString.append("\n").append(getTypeAndMessageLogString());
        if (throwablesList != null && throwablesList.size() > 0)
            logString.append("\n").append(geStackTracesLogString());
        return logString.toString();
    }

    /**
     * Get a minimal log friendly {@link String} for {@link Error} error parameters.
     *
     * @param error The {@link Error} to convert.
     * @return Returns the log friendly {@link String}.
     */
    public static String getMinimalErrorLogString(final Error error) {
        if (error == null)
            return "null";
        return error.getMinimalErrorLogString();
    }

    public String getMinimalErrorLogString() {
        StringBuilder logString = new StringBuilder();
        logString.append(getCodeString());
        logString.append(getTypeAndMessageLogString());
        return logString.toString();
    }

    /**
     * Get a minimal {@link String} for {@link Error} error parameters.
     *
     * @param error The {@link Error} to convert.
     * @return Returns the {@link String}.
     */
    public static String getMinimalErrorString(final Error error) {
        if (error == null)
            return "null";
        return error.getMinimalErrorString();
    }

    public String getMinimalErrorString() {
        StringBuilder logString = new StringBuilder();
        logString.append("(").append(getCode()).append(") ");
        logString.append(getType()).append(": ").append(getMessage());
        return logString.toString();
    }

    /**
     * Get a markdown {@link String} for {@link Error}.
     *
     * @param error The {@link Error} to convert.
     * @return Returns the markdown {@link String}.
     */
    public static String getErrorMarkdownString(final Error error) {
        if (error == null)
            return "null";
        return error.getErrorMarkdownString();
    }

    public String getErrorMarkdownString() {
        StringBuilder markdownString = new StringBuilder();
        markdownString.append(MarkdownUtils.getSingleLineMarkdownStringEntry("Error Code", getCode(), "-"));
        markdownString.append("\n").append(MarkdownUtils.getMultiLineMarkdownStringEntry((Errno.TYPE.equals(getType()) ? "Error Message" : "Error Message (" + getType() + ")"), message, "-"));
        if (throwablesList != null && throwablesList.size() > 0)
            markdownString.append("\n\n").append(geStackTracesMarkdownString());
        return markdownString.toString();
    }

    public String getCodeString() {
        return Logger.getSingleLineLogStringEntry("Error Code", code, "-");
    }

    public String getTypeAndMessageLogString() {
        return Logger.getMultiLineLogStringEntry(Errno.TYPE.equals(type) ? "Error Message" : "Error Message (" + type + ")", message, "-");
    }

    public String geStackTracesLogString() {
        return Logger.getStackTracesString("StackTraces:", Logger.getStackTracesStringArray(throwablesList));
    }

    public String geStackTracesMarkdownString() {
        return Logger.getStackTracesMarkdownString("StackTraces", Logger.getStackTracesStringArray(throwablesList));
    }
}
