using ShrinkNetwork;

namespace ShrinkNetwork.ServerHost.Framework;

public sealed class ServerHostOptions
{
    public int Port { get; set; } = 17777;
    public string UnityAssetsPath { get; set; } = ResolveDefaultUnityAssetsPath();
    public string ScanOutputDirectory { get; set; } = ResolveDefaultScanOutputDirectory();
    public string? SharedAuthToken { get; set; }
    public bool AllowAnonymousWhenAuthTokenMissing { get; set; }
    public bool EnableSessionTokens { get; set; } = true;
    public int SessionTokenTtlSeconds { get; set; } = 1800;
    public int SessionTokenRefreshWindowSeconds { get; set; } = 300;
    public bool DisconnectOnInvalidSessionToken { get; set; } = true;
    public bool EnableUnityCodeScan { get; set; } = true;
    public int MinProtocolVersion { get; set; } = ShrinkNetworkProtocol.CurrentProtocolVersion;
    public int MaxProtocolVersion { get; set; } = ShrinkNetworkProtocol.CurrentProtocolVersion;
    public int MinSchemaVersion { get; set; } = ShrinkNetworkProtocol.CurrentSchemaVersion;
    public int MaxSchemaVersion { get; set; } = ShrinkNetworkProtocol.CurrentSchemaVersion;
    public bool DisconnectOnProtocolViolation { get; set; } = true;
    public int DiagnosticsLogIntervalSeconds { get; set; } = 60;
    public bool EnableTcpTls { get; set; }
    public string? TcpTlsTargetHost { get; set; }
    public string? TcpTlsCertificatePath { get; set; }
    public string? TcpTlsCertificatePassword { get; set; }
    public bool TcpTlsCheckCertificateRevocation { get; set; }

    public void ApplyEnvironmentOverrides()
    {
        SharedAuthToken = ReadOptionalStringFromEnvironment("SHRINK_SERVER_AUTH_TOKEN", SharedAuthToken);
        AllowAnonymousWhenAuthTokenMissing = ReadBoolFromEnvironment(
            "SHRINK_SERVER_ALLOW_ANONYMOUS_WHEN_AUTH_TOKEN_MISSING",
            AllowAnonymousWhenAuthTokenMissing);
        EnableSessionTokens = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_SESSION_TOKENS", EnableSessionTokens);
        SessionTokenTtlSeconds = ReadIntFromEnvironment("SHRINK_SERVER_SESSION_TOKEN_TTL_SECONDS", SessionTokenTtlSeconds, 60);
        SessionTokenRefreshWindowSeconds = ReadIntFromEnvironment(
            "SHRINK_SERVER_SESSION_TOKEN_REFRESH_WINDOW_SECONDS",
            SessionTokenRefreshWindowSeconds,
            0);
        DisconnectOnInvalidSessionToken = ReadBoolFromEnvironment(
            "SHRINK_SERVER_DISCONNECT_ON_INVALID_SESSION_TOKEN",
            DisconnectOnInvalidSessionToken);
        EnableUnityCodeScan = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_UNITY_CODE_SCAN", EnableUnityCodeScan);
        MinProtocolVersion = ReadIntFromEnvironment("SHRINK_SERVER_MIN_PROTOCOL_VERSION", MinProtocolVersion, 1);
        MaxProtocolVersion = ReadIntFromEnvironment("SHRINK_SERVER_MAX_PROTOCOL_VERSION", MaxProtocolVersion, 1);
        MinSchemaVersion = ReadIntFromEnvironment("SHRINK_SERVER_MIN_SCHEMA_VERSION", MinSchemaVersion, 1);
        MaxSchemaVersion = ReadIntFromEnvironment("SHRINK_SERVER_MAX_SCHEMA_VERSION", MaxSchemaVersion, 1);
        DisconnectOnProtocolViolation = ReadBoolFromEnvironment(
            "SHRINK_SERVER_DISCONNECT_ON_PROTOCOL_VIOLATION",
            DisconnectOnProtocolViolation);
        DiagnosticsLogIntervalSeconds = ReadIntFromEnvironment(
            "SHRINK_SERVER_DIAGNOSTICS_LOG_INTERVAL_SECONDS",
            DiagnosticsLogIntervalSeconds,
            0);
        EnableTcpTls = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_TCP_TLS", EnableTcpTls);
        TcpTlsTargetHost = ReadOptionalStringFromEnvironment("SHRINK_SERVER_TLS_TARGET_HOST", TcpTlsTargetHost);
        TcpTlsCertificatePath = ReadOptionalStringFromEnvironment("SHRINK_SERVER_TLS_CERT_PATH", TcpTlsCertificatePath);
        TcpTlsCertificatePassword = ReadOptionalStringFromEnvironment(
            "SHRINK_SERVER_TLS_CERT_PASSWORD",
            TcpTlsCertificatePassword);
        TcpTlsCheckCertificateRevocation = ReadBoolFromEnvironment(
            "SHRINK_SERVER_TLS_CHECK_CERTIFICATE_REVOCATION",
            TcpTlsCheckCertificateRevocation);
    }

    public static string ResolveHostProjectDirectory()
    {
        var resolved = TryFindProjectDirectory(AppContext.BaseDirectory);
        if (!string.IsNullOrWhiteSpace(resolved))
            return resolved;

        resolved = TryFindProjectDirectory(Environment.CurrentDirectory);
        if (!string.IsNullOrWhiteSpace(resolved))
            return resolved;

        return Environment.CurrentDirectory;
    }

    public static string ResolveDefaultConfigFilePath()
    {
        return Path.Combine(ResolveHostProjectDirectory(), ServerHostProperties.DefaultFileName);
    }

    public static string ResolveDefaultUnityAssetsPath()
    {
        return Path.GetFullPath(Path.Combine(ResolveHostProjectDirectory(), "..", "..", "Assets"));
    }

    public static string ResolveDefaultScanOutputDirectory()
    {
        return Path.GetFullPath(Path.Combine(ResolveHostProjectDirectory(), "Generated"));
    }

    private static string? TryFindProjectDirectory(string startDirectory)
    {
        if (string.IsNullOrWhiteSpace(startDirectory) || !Directory.Exists(startDirectory))
            return null;

        var directory = new DirectoryInfo(Path.GetFullPath(startDirectory));
        for (var depth = 0; directory != null && depth < 8; depth++, directory = directory.Parent)
        {
            var candidate = Path.Combine(directory.FullName, "ShrinkNetwork.ServerHost.csproj");
            if (File.Exists(candidate))
                return directory.FullName;
        }

        return null;
    }

    private static int ReadIntFromEnvironment(string name, int fallback, int minValue)
    {
        var raw = Environment.GetEnvironmentVariable(name);
        if (!int.TryParse(raw, out var value))
            return fallback;

        return Math.Max(minValue, value);
    }

    private static bool ReadBoolFromEnvironment(string name, bool fallback)
    {
        var raw = Environment.GetEnvironmentVariable(name);
        if (string.IsNullOrWhiteSpace(raw))
            return fallback;

        if (bool.TryParse(raw, out var parsed))
            return parsed;

        return raw switch
        {
            "1" => true,
            "0" => false,
            _ => fallback
        };
    }

    private static string? ReadOptionalStringFromEnvironment(string name, string? fallback)
    {
        var raw = Environment.GetEnvironmentVariable(name);
        if (raw == null)
            return fallback;

        return string.IsNullOrWhiteSpace(raw) ? string.Empty : raw.Trim();
    }
}
