using NATS.Server.Configuration;

namespace NATS.Server.Tests;

public class NatsConfLexerTests
{
    [Fact]
    public void Lex_SimpleKeyStringValue_ReturnsKeyAndString()
    {
        var tokens = NatsConfLexer.Tokenize("foo = \"bar\"").ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[0].Value.ShouldBe("foo");
        tokens[1].Type.ShouldBe(TokenType.String);
        tokens[1].Value.ShouldBe("bar");
        tokens[2].Type.ShouldBe(TokenType.Eof);
    }

    [Fact]
    public void Lex_SingleQuotedString_ReturnsString()
    {
        var tokens = NatsConfLexer.Tokenize("foo = 'bar'").ToList();
        tokens[1].Type.ShouldBe(TokenType.String);
        tokens[1].Value.ShouldBe("bar");
    }

    [Fact]
    public void Lex_IntegerValue_ReturnsInteger()
    {
        var tokens = NatsConfLexer.Tokenize("port = 4222").ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[0].Value.ShouldBe("port");
        tokens[1].Type.ShouldBe(TokenType.Integer);
        tokens[1].Value.ShouldBe("4222");
    }

    [Fact]
    public void Lex_IntegerWithSuffix_ReturnsInteger()
    {
        var tokens = NatsConfLexer.Tokenize("size = 64mb").ToList();
        tokens[1].Type.ShouldBe(TokenType.Integer);
        tokens[1].Value.ShouldBe("64mb");
    }

    [Fact]
    public void Lex_BooleanValues_ReturnsBool()
    {
        foreach (var val in new[] { "true", "false", "yes", "no", "on", "off" })
        {
            var tokens = NatsConfLexer.Tokenize($"flag = {val}").ToList();
            tokens[1].Type.ShouldBe(TokenType.Bool);
        }
    }

    [Fact]
    public void Lex_FloatValue_ReturnsFloat()
    {
        var tokens = NatsConfLexer.Tokenize("rate = 2.5").ToList();
        tokens[1].Type.ShouldBe(TokenType.Float);
        tokens[1].Value.ShouldBe("2.5");
    }

    [Fact]
    public void Lex_NegativeNumber_ReturnsInteger()
    {
        var tokens = NatsConfLexer.Tokenize("offset = -10").ToList();
        tokens[1].Type.ShouldBe(TokenType.Integer);
        tokens[1].Value.ShouldBe("-10");
    }

    [Fact]
    public void Lex_DatetimeValue_ReturnsDatetime()
    {
        var tokens = NatsConfLexer.Tokenize("ts = 2024-01-15T10:30:00Z").ToList();
        tokens[1].Type.ShouldBe(TokenType.DateTime);
    }

    [Fact]
    public void Lex_HashComment_IsIgnored()
    {
        var tokens = NatsConfLexer.Tokenize("# this is a comment\nfoo = 1").ToList();
        var keys = tokens.Where(t => t.Type == TokenType.Key).ToList();
        keys.Count.ShouldBe(1);
        keys[0].Value.ShouldBe("foo");
    }

    [Fact]
    public void Lex_SlashComment_IsIgnored()
    {
        var tokens = NatsConfLexer.Tokenize("// comment\nfoo = 1").ToList();
        var keys = tokens.Where(t => t.Type == TokenType.Key).ToList();
        keys.Count.ShouldBe(1);
    }

    [Fact]
    public void Lex_MapBlock_ReturnsMapStartEnd()
    {
        var tokens = NatsConfLexer.Tokenize("auth { user: admin }").ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[0].Value.ShouldBe("auth");
        tokens[1].Type.ShouldBe(TokenType.MapStart);
        tokens[2].Type.ShouldBe(TokenType.Key);
        tokens[2].Value.ShouldBe("user");
        tokens[3].Type.ShouldBe(TokenType.String);
        tokens[3].Value.ShouldBe("admin");
        tokens[4].Type.ShouldBe(TokenType.MapEnd);
    }

    [Fact]
    public void Lex_Array_ReturnsArrayStartEnd()
    {
        var tokens = NatsConfLexer.Tokenize("items = [1, 2, 3]").ToList();
        tokens[1].Type.ShouldBe(TokenType.ArrayStart);
        tokens[2].Type.ShouldBe(TokenType.Integer);
        tokens[2].Value.ShouldBe("1");
        tokens[5].Type.ShouldBe(TokenType.ArrayEnd);
    }

    [Fact]
    public void Lex_Variable_ReturnsVariable()
    {
        var tokens = NatsConfLexer.Tokenize("secret = $MY_VAR").ToList();
        tokens[1].Type.ShouldBe(TokenType.Variable);
        tokens[1].Value.ShouldBe("MY_VAR");
    }

    [Fact]
    public void Lex_Include_ReturnsInclude()
    {
        var tokens = NatsConfLexer.Tokenize("include \"auth.conf\"").ToList();
        tokens[0].Type.ShouldBe(TokenType.Include);
        tokens[0].Value.ShouldBe("auth.conf");
    }

    [Fact]
    public void Lex_EscapeSequences_AreProcessed()
    {
        var tokens = NatsConfLexer.Tokenize("msg = \"hello\\tworld\\n\"").ToList();
        tokens[1].Type.ShouldBe(TokenType.String);
        tokens[1].Value.ShouldBe("hello\tworld\n");
    }

    [Fact]
    public void Lex_HexEscape_IsProcessed()
    {
        var tokens = NatsConfLexer.Tokenize("val = \"\\x41\\x42\"").ToList();
        tokens[1].Value.ShouldBe("AB");
    }

    [Fact]
    public void Lex_ColonSeparator_Works()
    {
        var tokens = NatsConfLexer.Tokenize("foo: bar").ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[1].Type.ShouldBe(TokenType.String);
    }

    [Fact]
    public void Lex_WhitespaceSeparator_Works()
    {
        var tokens = NatsConfLexer.Tokenize("foo bar").ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[1].Type.ShouldBe(TokenType.String);
    }

    [Fact]
    public void Lex_SemicolonTerminator_IsHandled()
    {
        var tokens = NatsConfLexer.Tokenize("foo = 1; bar = 2").ToList();
        var keys = tokens.Where(t => t.Type == TokenType.Key).ToList();
        keys.Count.ShouldBe(2);
    }

    [Fact]
    public void Lex_EmptyInput_ReturnsEof()
    {
        var tokens = NatsConfLexer.Tokenize("").ToList();
        tokens.Count.ShouldBe(1);
        tokens[0].Type.ShouldBe(TokenType.Eof);
    }

    [Fact]
    public void Lex_BlockString_ReturnsString()
    {
        var input = "desc (\nthis is\na block\n)\n";
        var tokens = NatsConfLexer.Tokenize(input).ToList();
        tokens[0].Type.ShouldBe(TokenType.Key);
        tokens[1].Type.ShouldBe(TokenType.String);
    }

    [Fact]
    public void Lex_IPAddress_ReturnsString()
    {
        var tokens = NatsConfLexer.Tokenize("host = 127.0.0.1").ToList();
        tokens[1].Type.ShouldBe(TokenType.String);
        tokens[1].Value.ShouldBe("127.0.0.1");
    }

    [Fact]
    public void Lex_TrackLineNumbers()
    {
        var tokens = NatsConfLexer.Tokenize("a = 1\nb = 2\nc = 3").ToList();
        tokens[0].Line.ShouldBe(1); // a
        tokens[2].Line.ShouldBe(2); // b
        tokens[4].Line.ShouldBe(3); // c
    }

    [Fact]
    public void Lex_UnterminatedString_ReturnsError()
    {
        var tokens = NatsConfLexer.Tokenize("foo = \"unterminated").ToList();
        tokens.ShouldContain(t => t.Type == TokenType.Error);
    }

    [Fact]
    public void Lex_StringStartingWithDigit_TreatedAsString()
    {
        var tokens = NatsConfLexer.Tokenize("foo = 3xyz").ToList();
        tokens[1].Type.ShouldBe(TokenType.String);
        tokens[1].Value.ShouldBe("3xyz");
    }
}
