using NATS.Server.Configuration;

namespace NATS.Server.Tests;

public class NatsConfParserTests
{
    [Fact]
    public void Parse_SimpleTopLevel_ReturnsCorrectTypes()
    {
        var result = NatsConfParser.Parse("foo = '1'; bar = 2.2; baz = true; boo = 22");
        result["foo"].ShouldBe("1");
        result["bar"].ShouldBe(2.2);
        result["baz"].ShouldBe(true);
        result["boo"].ShouldBe(22L);
    }

    [Fact]
    public void Parse_Booleans_AllVariants()
    {
        foreach (var (input, expected) in new[] {
            ("true", true), ("TRUE", true), ("yes", true), ("on", true),
            ("false", false), ("FALSE", false), ("no", false), ("off", false)
        })
        {
            var result = NatsConfParser.Parse($"flag = {input}");
            result["flag"].ShouldBe(expected);
        }
    }

    [Fact]
    public void Parse_IntegerWithSuffix_AppliesMultiplier()
    {
        var result = NatsConfParser.Parse("a = 1k; b = 2mb; c = 3gb; d = 4kb");
        result["a"].ShouldBe(1000L);
        result["b"].ShouldBe(2L * 1024 * 1024);
        result["c"].ShouldBe(3L * 1024 * 1024 * 1024);
        result["d"].ShouldBe(4L * 1024);
    }

    [Fact]
    public void Parse_NestedMap_ReturnsDictionary()
    {
        var result = NatsConfParser.Parse("auth { user: admin, pass: secret }");
        var auth = result["auth"].ShouldBeOfType<Dictionary<string, object?>>();
        auth["user"].ShouldBe("admin");
        auth["pass"].ShouldBe("secret");
    }

    [Fact]
    public void Parse_Array_ReturnsList()
    {
        var result = NatsConfParser.Parse("items = [1, 2, 3]");
        var items = result["items"].ShouldBeOfType<List<object?>>();
        items.Count.ShouldBe(3);
        items[0].ShouldBe(1L);
    }

    [Fact]
    public void Parse_Variable_ResolvesFromContext()
    {
        var result = NatsConfParser.Parse("index = 22\nfoo = $index");
        result["foo"].ShouldBe(22L);
    }

    [Fact]
    public void Parse_NestedVariable_UsesBlockScope()
    {
        var input = "index = 22\nnest {\n  index = 11\n  foo = $index\n}\nbar = $index";
        var result = NatsConfParser.Parse(input);
        var nest = result["nest"].ShouldBeOfType<Dictionary<string, object?>>();
        nest["foo"].ShouldBe(11L);
        result["bar"].ShouldBe(22L);
    }

    [Fact]
    public void Parse_EnvironmentVariable_ResolvesFromEnv()
    {
        Environment.SetEnvironmentVariable("NATS_TEST_VAR_12345", "hello");
        try
        {
            var result = NatsConfParser.Parse("val = $NATS_TEST_VAR_12345");
            result["val"].ShouldBe("hello");
        }
        finally
        {
            Environment.SetEnvironmentVariable("NATS_TEST_VAR_12345", null);
        }
    }

    [Fact]
    public void Parse_UndefinedVariable_Throws()
    {
        Should.Throw<FormatException>(() =>
            NatsConfParser.Parse("val = $UNDEFINED_VAR_XYZZY_99999"));
    }

    [Fact]
    public void Parse_IncludeDirective_MergesFile()
    {
        var dir = Path.Combine(Path.GetTempPath(), $"nats_test_{Guid.NewGuid():N}");
        Directory.CreateDirectory(dir);
        try
        {
            File.WriteAllText(Path.Combine(dir, "main.conf"), "port = 4222\ninclude \"sub.conf\"");
            File.WriteAllText(Path.Combine(dir, "sub.conf"), "host = \"localhost\"");
            var result = NatsConfParser.ParseFile(Path.Combine(dir, "main.conf"));
            result["port"].ShouldBe(4222L);
            result["host"].ShouldBe("localhost");
        }
        finally
        {
            Directory.Delete(dir, true);
        }
    }

    [Fact]
    public void Parse_MultipleKeySeparators_AllWork()
    {
        var r1 = NatsConfParser.Parse("a = 1");
        var r2 = NatsConfParser.Parse("a : 1");
        var r3 = NatsConfParser.Parse("a 1");
        r1["a"].ShouldBe(1L);
        r2["a"].ShouldBe(1L);
        r3["a"].ShouldBe(1L);
    }

    [Fact]
    public void Parse_ErrorOnInvalidInput_Throws()
    {
        Should.Throw<FormatException>(() => NatsConfParser.Parse("= invalid"));
    }

    [Fact]
    public void Parse_CommentsInsideBlocks_AreIgnored()
    {
        var input = "auth {\n  # comment\n  user: admin\n  // another comment\n  pass: secret\n}";
        var result = NatsConfParser.Parse(input);
        var auth = result["auth"].ShouldBeOfType<Dictionary<string, object?>>();
        auth["user"].ShouldBe("admin");
        auth["pass"].ShouldBe("secret");
    }

    [Fact]
    public void Parse_ArrayOfMaps_Works()
    {
        var input = "users = [\n  { user: alice, pass: pw1 }\n  { user: bob, pass: pw2 }\n]";
        var result = NatsConfParser.Parse(input);
        var users = result["users"].ShouldBeOfType<List<object?>>();
        users.Count.ShouldBe(2);
        var first = users[0].ShouldBeOfType<Dictionary<string, object?>>();
        first["user"].ShouldBe("alice");
    }

    [Fact]
    public void Parse_BcryptPassword_HandledAsString()
    {
        var input = "pass = $2a$04$P/.bd.7unw9Ew7yWJqXsl.f4oNRLQGvadEL2YnqQXbbb.IVQajRdK";
        var result = NatsConfParser.Parse(input);
        ((string)result["pass"]!).ShouldStartWith("$2a$");
    }

    [Fact]
    public void ParseFile_WithDigest_ReturnsStableHash()
    {
        var dir = Path.Combine(Path.GetTempPath(), $"nats_test_{Guid.NewGuid():N}");
        Directory.CreateDirectory(dir);
        try
        {
            var conf = Path.Combine(dir, "test.conf");
            File.WriteAllText(conf, "port = 4222\nhost = \"localhost\"");
            var (result, digest) = NatsConfParser.ParseFileWithDigest(conf);
            result["port"].ShouldBe(4222L);
            digest.ShouldStartWith("sha256:");
            digest.Length.ShouldBeGreaterThan(10);

            var (_, digest2) = NatsConfParser.ParseFileWithDigest(conf);
            digest2.ShouldBe(digest);
        }
        finally
        {
            Directory.Delete(dir, true);
        }
    }
}
