package me.proxylink.common;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public final class AgentConfigIO {
    private AgentConfigIO() {
    }

    public static AgentConfig load(Path path) throws IOException {
        Properties properties = new Properties();
        try (InputStream input = Files.newInputStream(path)) {
            properties.load(input);
        }
        Path baseDir = path.toAbsolutePath().getParent();
        return fromProperties(properties, baseDir);
    }

    public static AgentConfig fromProperties(Properties properties) {
        return fromProperties(properties, null);
    }

    public static AgentConfig fromProperties(Properties properties, Path baseDir) {
        AgentConfig.Role role = AgentConfig.Role.parse(required(properties, "role"));
        String nodeName = value(properties, "node.name", role.name().toLowerCase());

        AgentConfig.TunnelConfig tunnel = new AgentConfig.TunnelConfig(
                value(properties, "tunnel.listenHost", "0.0.0.0"),
                intValue(properties, "tunnel.listenPort", -1),
                value(properties, "tunnel.connectHost", ""),
                intValue(properties, "tunnel.connectPort", -1),
                required(properties, "tunnel.authToken"),
                boolValue(properties, "tunnel.tls.allowInsecure", false),
                intValue(properties, "tunnel.connectTimeoutMillis", 5000),
                longValue(properties, "tunnel.heartbeatIntervalMillis", 2000L),
                longValue(properties, "tunnel.heartbeatTimeoutMillis", 8000L),
                intValue(properties, "tunnel.heartbeatMissesBeforeDisconnect", 4),
                longValue(properties, "tunnel.reconnectInitialMillis", 250L),
                longValue(properties, "tunnel.reconnectMaxMillis", 10000L)
        );

        AgentConfig.TlsConfig tls = new AgentConfig.TlsConfig(
                boolValue(properties, "tunnel.tls.enabled", true),
                pathValue(properties, "tunnel.tls.keyStore", baseDir),
                value(properties, "tunnel.tls.keyStorePassword", ""),
                pathValue(properties, "tunnel.tls.trustStore", baseDir),
                value(properties, "tunnel.tls.trustStorePassword", ""),
                boolValue(properties, "tunnel.tls.requireClientAuth", false),
                boolValue(properties, "tunnel.tls.autoGenerate", true),
                boolValue(properties, "tunnel.tls.trustOnFirstUse", true),
                value(properties, "tunnel.tls.pinnedCertificateSha256", "")
        );

        return new AgentConfig(role, nodeName, tunnel, tls, frontends(properties, tunnel, tls), routes(properties));
    }

    private static List<AgentConfig.FrontendEndpointConfig> frontends(
            Properties properties,
            AgentConfig.TunnelConfig tunnel,
            AgentConfig.TlsConfig tls
    ) {
        String frontendsValue = value(properties, "frontends", "");
        List<AgentConfig.FrontendEndpointConfig> frontends = new ArrayList<>();

        if (frontendsValue.isBlank()) {
            if (!tunnel.connectHost().isBlank() && tunnel.connectPort() > 0) {
                frontends.add(new AgentConfig.FrontendEndpointConfig(
                        "default",
                        tunnel.connectHost(),
                        tunnel.connectPort(),
                        tls.pinnedCertificateSha256()
                ));
            }
            return frontends;
        }

        for (String rawName : frontendsValue.split(",")) {
            String name = rawName.trim();
            if (name.isEmpty()) {
                continue;
            }
            String prefix = "frontend." + name + ".";
            frontends.add(new AgentConfig.FrontendEndpointConfig(
                    name,
                    required(properties, prefix + "connectHost"),
                    intValue(properties, prefix + "connectPort", -1),
                    value(properties, prefix + "tls.pinnedCertificateSha256", "")
            ));
        }
        return frontends;
    }

    private static List<AgentConfig.RouteConfig> routes(Properties properties) {
        String routesValue = required(properties, "routes");
        List<AgentConfig.RouteConfig> routes = new ArrayList<>();
        for (String rawName : routesValue.split(",")) {
            String name = rawName.trim();
            if (name.isEmpty()) {
                continue;
            }
            String prefix = "route." + name + ".";
            routes.add(new AgentConfig.RouteConfig(
                    name,
                    value(properties, prefix + "frontendBindHost", "127.0.0.1"),
                    intValue(properties, prefix + "frontendBindPort", -1),
                    value(properties, prefix + "backendNode", ""),
                    value(properties, prefix + "backendTargetHost", "127.0.0.1"),
                    intValue(properties, prefix + "backendTargetPort", -1)
            ));
        }
        return routes;
    }

    private static String required(Properties properties, String key) {
        String value = properties.getProperty(key);
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException("Missing required config key: " + key);
        }
        return value.trim();
    }

    private static String value(Properties properties, String key, String defaultValue) {
        String value = properties.getProperty(key);
        if (value == null) {
            return defaultValue;
        }
        return value.trim();
    }

    private static int intValue(Properties properties, String key, int defaultValue) {
        String value = properties.getProperty(key);
        if (value == null || value.isBlank()) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value.trim());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Config key must be an integer: " + key, e);
        }
    }

    private static long longValue(Properties properties, String key, long defaultValue) {
        String value = properties.getProperty(key);
        if (value == null || value.isBlank()) {
            return defaultValue;
        }
        try {
            return Long.parseLong(value.trim());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Config key must be a long integer: " + key, e);
        }
    }

    private static boolean boolValue(Properties properties, String key, boolean defaultValue) {
        String value = properties.getProperty(key);
        if (value == null || value.isBlank()) {
            return defaultValue;
        }
        return Boolean.parseBoolean(value.trim());
    }

    private static Path pathValue(Properties properties, String key, Path baseDir) {
        String value = properties.getProperty(key);
        if (value == null || value.isBlank()) {
            return null;
        }
        Path path = Path.of(value.trim());
        if (!path.isAbsolute() && baseDir != null) {
            return baseDir.resolve(path).normalize();
        }
        return path;
    }
}
