using NATS.Server;
using NATS.Server.Configuration;

namespace NATS.Server.Tests;

public class ConfigProcessorTests
{
    private static string TestDataPath(string fileName) =>
        Path.Combine(AppContext.BaseDirectory, "TestData", fileName);

    // ─── Basic config ──────────────────────────────────────────────

    [Fact]
    public void BasicConf_Port()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.Port.ShouldBe(4222);
    }

    [Fact]
    public void BasicConf_Host()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.Host.ShouldBe("0.0.0.0");
    }

    [Fact]
    public void BasicConf_ServerName()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.ServerName.ShouldBe("test-server");
    }

    [Fact]
    public void BasicConf_MaxPayload()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxPayload.ShouldBe(2 * 1024 * 1024);
    }

    [Fact]
    public void BasicConf_MaxConnections()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxConnections.ShouldBe(1000);
    }

    [Fact]
    public void BasicConf_Debug()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.Debug.ShouldBeTrue();
    }

    [Fact]
    public void BasicConf_Trace()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.Trace.ShouldBeFalse();
    }

    [Fact]
    public void BasicConf_PingInterval()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.PingInterval.ShouldBe(TimeSpan.FromSeconds(30));
    }

    [Fact]
    public void BasicConf_MaxPingsOut()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxPingsOut.ShouldBe(3);
    }

    [Fact]
    public void BasicConf_WriteDeadline()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void BasicConf_MaxSubs()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxSubs.ShouldBe(100);
    }

    [Fact]
    public void BasicConf_MaxSubTokens()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxSubTokens.ShouldBe(16);
    }

    [Fact]
    public void BasicConf_MaxControlLine()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxControlLine.ShouldBe(2048);
    }

    [Fact]
    public void BasicConf_MaxPending()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MaxPending.ShouldBe(32L * 1024 * 1024);
    }

    [Fact]
    public void BasicConf_LameDuckDuration()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.LameDuckDuration.ShouldBe(TimeSpan.FromSeconds(60));
    }

    [Fact]
    public void BasicConf_LameDuckGracePeriod()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.LameDuckGracePeriod.ShouldBe(TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void BasicConf_MonitorPort()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.MonitorPort.ShouldBe(8222);
    }

    [Fact]
    public void BasicConf_Logtime()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("basic.conf"));
        opts.Logtime.ShouldBeTrue();
        opts.LogtimeUTC.ShouldBeFalse();
    }

    // ─── Auth config ───────────────────────────────────────────────

    [Fact]
    public void AuthConf_SimpleUser()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        opts.Username.ShouldBe("admin");
        opts.Password.ShouldBe("s3cret");
    }

    [Fact]
    public void AuthConf_AuthTimeout()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        opts.AuthTimeout.ShouldBe(TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void AuthConf_NoAuthUser()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        opts.NoAuthUser.ShouldBe("guest");
    }

    [Fact]
    public void AuthConf_UsersArray()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        opts.Users.ShouldNotBeNull();
        opts.Users.Count.ShouldBe(2);
    }

    [Fact]
    public void AuthConf_AliceUser()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        var alice = opts.Users!.First(u => u.Username == "alice");
        alice.Password.ShouldBe("pw1");
        alice.Permissions.ShouldNotBeNull();
        alice.Permissions!.Publish.ShouldNotBeNull();
        alice.Permissions.Publish!.Allow.ShouldNotBeNull();
        alice.Permissions.Publish.Allow!.ShouldContain("foo.>");
        alice.Permissions.Subscribe.ShouldNotBeNull();
        alice.Permissions.Subscribe!.Allow.ShouldNotBeNull();
        alice.Permissions.Subscribe.Allow!.ShouldContain(">");
    }

    [Fact]
    public void AuthConf_BobUser()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("auth.conf"));
        var bob = opts.Users!.First(u => u.Username == "bob");
        bob.Password.ShouldBe("pw2");
        bob.Permissions.ShouldBeNull();
    }

    // ─── TLS config ────────────────────────────────────────────────

    [Fact]
    public void TlsConf_CertFiles()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsCert.ShouldBe("/path/to/cert.pem");
        opts.TlsKey.ShouldBe("/path/to/key.pem");
        opts.TlsCaCert.ShouldBe("/path/to/ca.pem");
    }

    [Fact]
    public void TlsConf_Verify()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsVerify.ShouldBeTrue();
        opts.TlsMap.ShouldBeTrue();
    }

    [Fact]
    public void TlsConf_Timeout()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsTimeout.ShouldBe(TimeSpan.FromSeconds(3));
    }

    [Fact]
    public void TlsConf_RateLimit()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsRateLimit.ShouldBe(100);
    }

    [Fact]
    public void TlsConf_PinnedCerts()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsPinnedCerts.ShouldNotBeNull();
        opts.TlsPinnedCerts!.Count.ShouldBe(1);
        opts.TlsPinnedCerts.ShouldContain("abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789");
    }

    [Fact]
    public void TlsConf_HandshakeFirst()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.TlsHandshakeFirst.ShouldBeTrue();
    }

    [Fact]
    public void TlsConf_AllowNonTls()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.AllowNonTls.ShouldBeFalse();
    }

    // ─── Full config ───────────────────────────────────────────────

    [Fact]
    public void FullConf_CoreOptions()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.Port.ShouldBe(4222);
        opts.Host.ShouldBe("0.0.0.0");
        opts.ServerName.ShouldBe("full-test");
        opts.ClientAdvertise.ShouldBe("nats://public.example.com:4222");
    }

    [Fact]
    public void FullConf_Limits()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.MaxPayload.ShouldBe(1024 * 1024);
        opts.MaxControlLine.ShouldBe(4096);
        opts.MaxConnections.ShouldBe(65536);
        opts.MaxPending.ShouldBe(64L * 1024 * 1024);
        opts.MaxSubs.ShouldBe(0);
        opts.MaxSubTokens.ShouldBe(0);
        opts.MaxTracedMsgLen.ShouldBe(1024);
        opts.DisableSublistCache.ShouldBeFalse();
        opts.MaxClosedClients.ShouldBe(5000);
    }

    [Fact]
    public void FullConf_Logging()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.Debug.ShouldBeFalse();
        opts.Trace.ShouldBeFalse();
        opts.TraceVerbose.ShouldBeFalse();
        opts.Logtime.ShouldBeTrue();
        opts.LogtimeUTC.ShouldBeFalse();
        opts.LogFile.ShouldBe("/var/log/nats.log");
        opts.LogSizeLimit.ShouldBe(100L * 1024 * 1024);
        opts.LogMaxFiles.ShouldBe(5);
    }

    [Fact]
    public void FullConf_Monitoring()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.MonitorPort.ShouldBe(8222);
        opts.MonitorBasePath.ShouldBe("/nats");
    }

    [Fact]
    public void FullConf_Files()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.PidFile.ShouldBe("/var/run/nats.pid");
        opts.PortsFileDir.ShouldBe("/var/run");
    }

    [Fact]
    public void FullConf_Lifecycle()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.LameDuckDuration.ShouldBe(TimeSpan.FromMinutes(2));
        opts.LameDuckGracePeriod.ShouldBe(TimeSpan.FromSeconds(10));
    }

    [Fact]
    public void FullConf_Tags()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.Tags.ShouldNotBeNull();
        opts.Tags!["region"].ShouldBe("us-east");
        opts.Tags["env"].ShouldBe("production");
    }

    [Fact]
    public void FullConf_Auth()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.Username.ShouldBe("admin");
        opts.Password.ShouldBe("secret");
        opts.AuthTimeout.ShouldBe(TimeSpan.FromSeconds(2));
    }

    [Fact]
    public void FullConf_Tls()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("full.conf"));
        opts.TlsCert.ShouldBe("/path/to/cert.pem");
        opts.TlsKey.ShouldBe("/path/to/key.pem");
        opts.TlsCaCert.ShouldBe("/path/to/ca.pem");
        opts.TlsVerify.ShouldBeTrue();
        opts.TlsTimeout.ShouldBe(TimeSpan.FromSeconds(2));
        opts.TlsHandshakeFirst.ShouldBeTrue();
    }

    // ─── Listen combined format ────────────────────────────────────

    [Fact]
    public void ListenCombined_HostAndPort()
    {
        var opts = ConfigProcessor.ProcessConfig("listen: \"10.0.0.1:5222\"");
        opts.Host.ShouldBe("10.0.0.1");
        opts.Port.ShouldBe(5222);
    }

    [Fact]
    public void ListenCombined_PortOnly()
    {
        var opts = ConfigProcessor.ProcessConfig("listen: \":5222\"");
        opts.Port.ShouldBe(5222);
    }

    [Fact]
    public void ListenCombined_BarePort()
    {
        var opts = ConfigProcessor.ProcessConfig("listen: 5222");
        opts.Port.ShouldBe(5222);
    }

    // ─── HTTP combined format ──────────────────────────────────────

    [Fact]
    public void HttpCombined_HostAndPort()
    {
        var opts = ConfigProcessor.ProcessConfig("http: \"10.0.0.1:8333\"");
        opts.MonitorHost.ShouldBe("10.0.0.1");
        opts.MonitorPort.ShouldBe(8333);
    }

    [Fact]
    public void HttpsCombined_HostAndPort()
    {
        var opts = ConfigProcessor.ProcessConfig("https: \"10.0.0.1:8444\"");
        opts.MonitorHost.ShouldBe("10.0.0.1");
        opts.MonitorHttpsPort.ShouldBe(8444);
    }

    // ─── Duration as number ────────────────────────────────────────

    [Fact]
    public void DurationAsNumber_TreatedAsSeconds()
    {
        var opts = ConfigProcessor.ProcessConfig("ping_interval: 60");
        opts.PingInterval.ShouldBe(TimeSpan.FromSeconds(60));
    }

    [Fact]
    public void DurationAsString_Milliseconds()
    {
        var opts = ConfigProcessor.ProcessConfig("write_deadline: \"500ms\"");
        opts.WriteDeadline.ShouldBe(TimeSpan.FromMilliseconds(500));
    }

    [Fact]
    public void DurationAsString_Hours()
    {
        var opts = ConfigProcessor.ProcessConfig("ping_interval: \"1h\"");
        opts.PingInterval.ShouldBe(TimeSpan.FromHours(1));
    }

    // ─── Unknown keys ──────────────────────────────────────────────

    [Fact]
    public void UnknownKeys_SilentlyIgnored()
    {
        var opts = ConfigProcessor.ProcessConfig("""
            port: 4222
            cluster { name: "my-cluster" }
            jetstream { store_dir: "/tmp/js" }
            unknown_key: "whatever"
            """);
        opts.Port.ShouldBe(4222);
    }

    // ─── Server name validation ────────────────────────────────────

    [Fact]
    public void ServerNameWithSpaces_ReportsError()
    {
        var ex = Should.Throw<ConfigProcessorException>(() =>
            ConfigProcessor.ProcessConfig("server_name: \"my server\""));
        ex.Errors.ShouldContain(e => e.Contains("server_name cannot contain spaces"));
    }

    // ─── Max sub tokens validation ─────────────────────────────────

    [Fact]
    public void MaxSubTokens_ExceedsLimit_ReportsError()
    {
        var ex = Should.Throw<ConfigProcessorException>(() =>
            ConfigProcessor.ProcessConfig("max_sub_tokens: 300"));
        ex.Errors.ShouldContain(e => e.Contains("max_sub_tokens cannot exceed 256"));
    }

    // ─── ProcessConfig from string ─────────────────────────────────

    [Fact]
    public void ProcessConfig_FromString()
    {
        var opts = ConfigProcessor.ProcessConfig("""
            port: 9222
            host: "127.0.0.1"
            debug: true
            """);
        opts.Port.ShouldBe(9222);
        opts.Host.ShouldBe("127.0.0.1");
        opts.Debug.ShouldBeTrue();
    }

    // ─── TraceVerbose sets Trace ────────────────────────────────────

    [Fact]
    public void TraceVerbose_AlsoSetsTrace()
    {
        var opts = ConfigProcessor.ProcessConfig("trace_verbose: true");
        opts.TraceVerbose.ShouldBeTrue();
        opts.Trace.ShouldBeTrue();
    }

    // ─── Error collection (not fail-fast) ──────────────────────────

    [Fact]
    public void MultipleErrors_AllCollected()
    {
        var ex = Should.Throw<ConfigProcessorException>(() =>
            ConfigProcessor.ProcessConfig("""
                server_name: "bad name"
                max_sub_tokens: 999
                """));
        ex.Errors.Count.ShouldBe(2);
        ex.Errors.ShouldContain(e => e.Contains("server_name"));
        ex.Errors.ShouldContain(e => e.Contains("max_sub_tokens"));
    }

    // ─── ConfigFile path tracking ──────────────────────────────────

    [Fact]
    public void ProcessConfigFile_SetsConfigFilePath()
    {
        var path = TestDataPath("basic.conf");
        var opts = ConfigProcessor.ProcessConfigFile(path);
        opts.ConfigFile.ShouldBe(path);
    }

    // ─── HasTls derived property ───────────────────────────────────

    [Fact]
    public void HasTls_TrueWhenCertAndKeySet()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("tls.conf"));
        opts.HasTls.ShouldBeTrue();
    }

    // ─── MQTT config ────────────────────────────────────────────

    [Fact]
    public void MqttConf_ListenHostAndPort()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.Host.ShouldBe("10.0.0.1");
        opts.Mqtt.Port.ShouldBe(1883);
    }

    [Fact]
    public void MqttConf_NoAuthUser()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.NoAuthUser.ShouldBe("mqtt_default");
    }

    [Fact]
    public void MqttConf_Authorization()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.Username.ShouldBe("mqtt_user");
        opts.Mqtt.Password.ShouldBe("mqtt_pass");
        opts.Mqtt.Token.ShouldBe("mqtt_token");
        opts.Mqtt.AuthTimeout.ShouldBe(3.0);
    }

    [Fact]
    public void MqttConf_Tls()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.TlsCert.ShouldBe("/path/to/mqtt-cert.pem");
        opts.Mqtt.TlsKey.ShouldBe("/path/to/mqtt-key.pem");
        opts.Mqtt.TlsCaCert.ShouldBe("/path/to/mqtt-ca.pem");
        opts.Mqtt.TlsVerify.ShouldBeTrue();
        opts.Mqtt.TlsTimeout.ShouldBe(5.0);
        opts.Mqtt.HasTls.ShouldBeTrue();
    }

    [Fact]
    public void MqttConf_QosSettings()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.AckWait.ShouldBe(TimeSpan.FromSeconds(60));
        opts.Mqtt.MaxAckPending.ShouldBe((ushort)2048);
        opts.Mqtt.JsApiTimeout.ShouldBe(TimeSpan.FromSeconds(10));
    }

    [Fact]
    public void MqttConf_JetStreamSettings()
    {
        var opts = ConfigProcessor.ProcessConfigFile(TestDataPath("mqtt.conf"));
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.JsDomain.ShouldBe("mqtt-domain");
        opts.Mqtt.StreamReplicas.ShouldBe(3);
        opts.Mqtt.ConsumerReplicas.ShouldBe(1);
        opts.Mqtt.ConsumerMemoryStorage.ShouldBeTrue();
        opts.Mqtt.ConsumerInactiveThreshold.ShouldBe(TimeSpan.FromMinutes(5));
    }

    [Fact]
    public void MqttConf_MaxAckPendingValidation_ReportsError()
    {
        var ex = Should.Throw<ConfigProcessorException>(() =>
            ConfigProcessor.ProcessConfig("""
                mqtt {
                    max_ack_pending: 70000
                }
                """));
        ex.Errors.ShouldContain(e => e.Contains("max_ack_pending"));
    }

    [Fact]
    public void MqttConf_Aliases()
    {
        // Test alias keys: "ackwait" (alias for "ack_wait"), "net" (alias for "host"),
        // "max_inflight" (alias for "max_ack_pending"), "consumer_auto_cleanup" (alias)
        var opts = ConfigProcessor.ProcessConfig("""
            mqtt {
                net: "127.0.0.1"
                port: 1884
                ackwait: "45s"
                max_inflight: 500
                api_timeout: "8s"
                consumer_auto_cleanup: "10m"
            }
            """);
        opts.Mqtt.ShouldNotBeNull();
        opts.Mqtt!.Host.ShouldBe("127.0.0.1");
        opts.Mqtt.Port.ShouldBe(1884);
        opts.Mqtt.AckWait.ShouldBe(TimeSpan.FromSeconds(45));
        opts.Mqtt.MaxAckPending.ShouldBe((ushort)500);
        opts.Mqtt.JsApiTimeout.ShouldBe(TimeSpan.FromSeconds(8));
        opts.Mqtt.ConsumerInactiveThreshold.ShouldBe(TimeSpan.FromMinutes(10));
    }

    [Fact]
    public void MqttConf_Absent_ReturnsNull()
    {
        var opts = ConfigProcessor.ProcessConfig("port: 4222");
        opts.Mqtt.ShouldBeNull();
    }
}
