package io.xpipe.app.issue;

import io.xpipe.app.core.mode.AppOperationMode;
import io.xpipe.app.process.ProcessOutputException;
import io.xpipe.app.util.OsType;

import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.util.*;

public class ErrorEventFactory {

    private static final Map<Throwable, ErrorEvent.ErrorEventBuilder> EVENT_BASES = new IdentityHashMap<>();

    public static ErrorEvent.ErrorEventBuilder fromThrowable(Throwable t) {
        var b = retrieveBuilder(t);
        return b;
    }

    public static ErrorEvent.ErrorEventBuilder fromThrowable(String msg, Throwable t) {
        var b = retrieveBuilder(t);
        return b.description(msg);
    }

    public static ErrorEvent.ErrorEventBuilder fromMessage(String msg) {
        return ErrorEvent.builder().description(msg);
    }

    public static <T extends Throwable> T expectedIfContains(T t, String... s) {
        return expectedIf(
                t,
                t.getMessage() != null
                        && Arrays.stream(s).map(String::toLowerCase).anyMatch(string -> t.getMessage()
                                .toLowerCase(Locale.ROOT)
                                .contains(string)));
    }

    public static <T extends Throwable> T expectedIf(T t, boolean b) {
        if (b) {
            preconfigure(fromThrowable(t).expected());
        }
        return t;
    }

    public static <T extends Throwable> T expected(T t) {
        preconfigure(fromThrowable(t).expected());
        return t;
    }

    public static synchronized void preconfigure(ErrorEvent.ErrorEventBuilder event) {
        var found = EVENT_BASES.get(event.getThrowable());
        if (found != null) {
            found.apply(event);
            return;
        }

        EVENT_BASES.put(event.getThrowable(), event);
    }

    private static synchronized ErrorEvent.ErrorEventBuilder retrieveBuilder(Throwable t) {
        var b = EVENT_BASES.remove(t);
        if (b == null) {
            b = ErrorEvent.builder().throwable(t);
        }

        var chain = new ArrayList<Throwable>();
        var current = t;
        while (current != null) {
            chain.addFirst(current);
            current = current.getCause();
        }
        for (Throwable pre : chain) {
            var found = EVENT_BASES.get(pre);
            if (found != null) {
                b.apply(found);
            }
        }

        // Indicates that the session is scheduled to end and new processes won't be started
        if (OsType.ofLocal() == OsType.WINDOWS
                && t instanceof ProcessOutputException pex
                && pex.getExitCode() == -1073741205) {
            b.expected();
        }

        // On Linux shutdown, active file descriptors are getting closed. This breaks shell commands
        if (OsType.ofLocal() == OsType.LINUX
                && AppOperationMode.isInShutdown()
                && t instanceof IllegalStateException ise
                && "Parent stream is closed".equals(ise.getMessage())) {
            b.expected();
        }

        if (OsType.ofLocal() == OsType.WINDOWS
                && t.getMessage() != null
                && t.getMessage().contains("The cloud file provider is not running")) {
            b.description(
                    "The OneDrive cloud file provider is not running. Verify that your cloud storage is working and you are logged in.");
            b.expected();
        }

        if (t instanceof AccessDeniedException ade) {
            b.description("Access is denied: " + ade.getMessage());
            b.expected();
        }

        if (t instanceof NoSuchFileException nsfe) {
            b.description("No such file: " + nsfe.getMessage());
            b.expected();
        }

        return b;
    }
}
