package me.proxylink.plugin;

import me.proxylink.common.AgentConfig;
import me.proxylink.common.AgentConfigIO;
import me.proxylink.plugin.tls.TlsBootstrap;
import me.proxylink.plugin.tunnel.BackendAgent;
import me.proxylink.plugin.tunnel.FrontendAgent;
import me.proxylink.plugin.tunnel.ManagedAgent;
import me.proxylink.plugin.tunnel.TunnelStatus;
import me.proxylink.plugin.web.WebWizardServer;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class PluginRuntime {
    private final Logger logger;
    private ManagedAgent agent;
    private WebWizardServer webWizard;
    private Path configPath;
    private Path dataFolder;
    private String defaultConfigResource;
    private AgentConfig.Role requiredRole;
    private AgentConfig config;
    private String lastStartupError = "";

    public PluginRuntime(Logger logger) {
        this.logger = Objects.requireNonNull(logger, "logger");
    }

    public boolean start(Path dataFolder, String defaultConfigResource, AgentConfig.Role requiredRole) {
        this.dataFolder = dataFolder;
        this.defaultConfigResource = defaultConfigResource;
        this.requiredRole = requiredRole;
        try {
            Files.createDirectories(dataFolder);
            configPath = dataFolder.resolve("config.properties");
            ensureConfigExists(configPath, defaultConfigResource);
            SecretTokenBootstrap.prepare(configPath, logger);

            config = AgentConfigIO.load(configPath);
            config = TlsBootstrap.prepare(dataFolder, configPath, config, logger);
            config.validateOrThrow();

            if (config.role() != requiredRole) {
                throw new IllegalArgumentException("This plugin entrypoint requires role=" + requiredRole.name().toLowerCase()
                        + " but config.properties has role=" + config.role().name().toLowerCase());
            }

            agent = createAgent(config);
            agent.start();
            lastStartupError = "";
            logger.info("DirtSimpleP2P started role=" + config.role().name().toLowerCase()
                    + " node=" + config.nodeName());
            return true;
        } catch (Exception e) {
            lastStartupError = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
            logger.log(Level.SEVERE, "DirtSimpleP2P did not start. Fix plugins/DirtSimpleP2P/config.properties and restart.", e);
            stop();
            return false;
        }
    }

    public void stop() {
        if (webWizard != null) {
            webWizard.close();
            webWizard = null;
        }
        if (agent != null) {
            agent.close();
            agent = null;
        }
    }

    public List<String> statusLines() {
        TunnelStatus status = status();
        List<String> lines = new ArrayList<>();
        lines.add("Role: " + lower(status.role()));
        lines.add("Node: " + status.nodeName());
        lines.add("Agent: " + (status.running() ? "running" : "stopped"));
        lines.add("Tunnel: " + (status.tunnelConnected() ? "connected" : "not connected"));
        lines.add("Connected tunnels: " + status.connectedTunnels());
        lines.add("Active streams: " + status.activeStreams());
        lines.add("TLS: " + (status.tlsEnabled() ? "enabled" : "disabled"));
        lines.add("Web wizard: " + webWizardStatus());
        lines.add("Last event: " + status.lastEvent());
        if (!lastStartupError.isBlank()) {
            lines.add("Startup error: " + lastStartupError);
        }
        return lines;
    }

    public List<String> doctorLines() {
        List<String> lines = new ArrayList<>();
        lines.add("Checking DirtSimpleP2P...");

        if (configPath == null) {
            lines.add("Config: not loaded yet");
            return lines;
        }

        lines.add("Config file: " + configPath);
        try {
            AgentConfig loaded = AgentConfigIO.load(configPath);
            loaded.validateOrThrow();
            lines.add("Config: valid");
            lines.add("Role: " + lower(loaded.role()));
            lines.add("TLS: " + (loaded.tls().enabled() ? "enabled" : "disabled"));
            lines.add("Routes: " + loaded.routes().size());
            if (loaded.role() == AgentConfig.Role.BACKEND) {
                lines.add("Frontend endpoints: " + loaded.frontends().size());
            }

            if (!loaded.tls().enabled()) {
                lines.add("Security: TLS is disabled. This is okay for local testing, not ideal for paid production use.");
            } else if (loaded.role() == AgentConfig.Role.FRONTEND && loaded.tls().keyStorePath() != null) {
                lines.add("TLS keystore: " + loaded.tls().keyStorePath());
            } else if (loaded.role() == AgentConfig.Role.BACKEND && loaded.tls().pinnedCertificateSha256().isBlank()) {
                lines.add("TLS pin: not pinned yet. It will be saved after the first successful TLS connection.");
            } else if (loaded.role() == AgentConfig.Role.BACKEND) {
                lines.add("TLS pin: configured");
            }
        } catch (Exception e) {
            lines.add("Config problem: " + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
        }

        lines.addAll(statusLines());
        return lines;
    }

    public synchronized List<String> toggleWebWizardLines() {
        List<String> lines = new ArrayList<>();
        if (configPath == null) {
            lines.add("Web wizard: unavailable because config.properties is not loaded");
            return lines;
        }

        if (webWizard != null && webWizard.isRunning()) {
            webWizard.close();
            webWizard = null;
            lines.add("Web wizard: stopped");
            return lines;
        }

        try {
            webWizard = WebWizardServer.start(configPath, logger, this::reloadFromConfig);
            lines.add("Web wizard: running");
            lines.add("URL: " + webWizard.uri());
            lines.add("Login key: printed in the server console");
            lines.add("Run /dsp2p webwizard again to stop it");
        } catch (Exception e) {
            lines.add("Web wizard failed to start: "
                    + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
            logger.log(Level.WARNING, "Unable to start DirtSimpleP2P Web Wizard", e);
        }
        return lines;
    }

    public synchronized String reloadFromConfig() {
        if (dataFolder == null || configPath == null || defaultConfigResource == null || requiredRole == null) {
            return "Reload is unavailable because DirtSimpleP2P has not finished initializing.";
        }

        try {
            ensureConfigExists(configPath, defaultConfigResource);
            SecretTokenBootstrap.prepare(configPath, logger);

            AgentConfig loaded = AgentConfigIO.load(configPath);
            loaded = TlsBootstrap.prepare(dataFolder, configPath, loaded, logger);
            loaded.validateOrThrow();

            if (loaded.role() != requiredRole) {
                throw new IllegalArgumentException("This plugin entrypoint requires role=" + requiredRole.name().toLowerCase()
                        + " but config.properties has role=" + loaded.role().name().toLowerCase());
            }

            ManagedAgent previous = agent;
            agent = null;
            if (previous != null) {
                previous.close();
            }

            ManagedAgent replacement = createAgent(loaded);
            replacement.start();
            agent = replacement;
            config = loaded;
            lastStartupError = "";

            String message = "Reloaded DirtSimpleP2P config and restarted the tunnel agent.";
            logger.info(message);
            return message;
        } catch (Exception e) {
            lastStartupError = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
            logger.log(Level.SEVERE, "DirtSimpleP2P reload failed. Fix config.properties and reload again.", e);
            return "Reload failed: " + lastStartupError;
        }
    }

    public TunnelStatus status() {
        if (agent != null) {
            return agent.status();
        }
        if (config != null) {
            return TunnelStatus.stopped(config.role(), config.nodeName(), config.tls().enabled(),
                    lastStartupError.isBlank() ? "not running" : "startup failed");
        }
        return TunnelStatus.stopped(AgentConfig.Role.BACKEND, "unknown", false,
                lastStartupError.isBlank() ? "not running" : "startup failed");
    }

    private ManagedAgent createAgent(AgentConfig config) {
        if (config.role() == AgentConfig.Role.FRONTEND) {
            return new FrontendAgent(config);
        }
        return new BackendAgent(config, this::saveLearnedTlsFingerprint);
    }

    private void saveLearnedTlsFingerprint(String endpointName, String fingerprint) {
        if (configPath == null || fingerprint == null || fingerprint.isBlank()) {
            return;
        }
        try {
            ConfigFileEditor.setProperty(configPath,
                    "frontend." + endpointName + ".tls.pinnedCertificateSha256",
                    fingerprint);
            if (config != null && config.frontends().size() == 1) {
                ConfigFileEditor.setProperty(configPath, "tunnel.tls.pinnedCertificateSha256", fingerprint);
            }
            config = AgentConfigIO.load(configPath);
            logger.info("Saved frontend TLS certificate pin for " + endpointName + " to " + configPath);
        } catch (Exception e) {
            logger.log(Level.WARNING, "Unable to save frontend TLS certificate pin", e);
        }
    }

    private void ensureConfigExists(Path configPath, String resourceName) throws IOException {
        if (Files.exists(configPath)) {
            return;
        }

        try (InputStream input = PluginRuntime.class.getClassLoader().getResourceAsStream(resourceName)) {
            if (input == null) {
                throw new IOException("Missing bundled config resource: " + resourceName);
            }
            Files.copy(input, configPath, StandardCopyOption.COPY_ATTRIBUTES);
        }

        logger.warning("Created default DirtSimpleP2P config at " + configPath
                + ". Edit the secret token and network settings, then restart the server.");
    }

    private static String lower(AgentConfig.Role role) {
        return role.name().toLowerCase(java.util.Locale.ROOT);
    }

    private String webWizardStatus() {
        if (webWizard == null || !webWizard.isRunning()) {
            return "stopped";
        }
        return "running at " + webWizard.uri();
    }
}
