using Inspectron.Epson.Templates.Language;

namespace Inspectron.Epson.Templates.Tests.Language;

public class CommentTests
{
    #region Single-line comments

    [Fact]
    public void Tokenize_SingleLineComment_ProducesNoTextTokens()
    {
        var lexer = new Lexer("@* This is a comment");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
    }

    [Fact]
    public void Tokenize_InlineSingleLineComment_ProducesNoTextTokens()
    {
        var lexer = new Lexer("@* This is a comment *@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
    }

    [Fact]
    public void Tokenize_IndentedSingleLineComment_IsRecognizedAsComment()
    {
        var lexer = new Lexer("    @* indented comment");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
    }

    [Fact]
    public void Tokenize_TextBeforeAndAfterCommentLine_IsPreserved()
    {
        var lexer = new Lexer("Before\n@* comment\nAfter");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Before");
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
    }

    [Fact]
    public void Tokenize_InlineCommentWithContentAfter_PreservesContentAfter()
    {
        var lexer = new Lexer("@* comment *@After");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
    }

    #endregion

    #region Multi-line comments

    [Fact]
    public void Tokenize_MultiLineComment_ProducesNoTextTokens()
    {
        var lexer = new Lexer("@*\nThis is multi-line\ncomment text\n*@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
    }

    [Fact]
    public void Tokenize_MultiLineComment_ContentBeforeAndAfterPreserved()
    {
        var lexer = new Lexer("Before\n@*\ncomment\n*@\nAfter");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Before");
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
    }

    [Fact]
    public void Tokenize_UnclosedMultiLineComment_ProducesError()
    {
        var lexer = new Lexer("@*\nThis comment is never closed");
        var result = lexer.Tokenize();

        Assert.True(result.HasErrors);
        Assert.Contains(result.Errors, e => e.Message.Contains("Unclosed multi-line comment"));
    }

    [Fact]
    public void Tokenize_MultiLineCommentWithContentAfterCloser_PreservesContent()
    {
        var lexer = new Lexer("@*\ncomment\n*@After");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
    }

    #endregion

    #region Edge cases

    [Fact]
    public void Tokenize_StarSeparator_StillWorksNotConfusedWithComment()
    {
        var lexer = new Lexer("***");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.StarSeparator);
    }

    [Fact]
    public void Tokenize_DirectivesInsideComment_AreIgnored()
    {
        var lexer = new Lexer("@*\n@if condition\n@foreach item in items\n*@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.If);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Foreach);
    }

    [Fact]
    public void Tokenize_BindingsInsideComment_AreIgnored()
    {
        var lexer = new Lexer("@* {PropertyName} *@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Binding);
    }

    [Fact]
    public void Tokenize_CommentWithMultipleStars_HandledCorrectly()
    {
        var lexer = new Lexer("@*** This has extra stars ***@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("stars"));
    }

    [Fact]
    public void Tokenize_EmptyMultiLineComment_HandledCorrectly()
    {
        var lexer = new Lexer("@*\n*@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
    }

    [Fact]
    public void Tokenize_CommentInComplexTemplate_WorksCorrectly()
    {
        var template = @"#bold,center# Title #
@* This is a header comment *@
---
@if HasItems
@* Loop through items *@
@foreach item in Items
{item.Name}
@end
@end";
        var lexer = new Lexer(template);
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.StyleStart);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.DashSeparator);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.If);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Foreach);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
    }

    [Fact]
    public void Tokenize_MultipleCommentsInTemplate_AllStripped()
    {
        var template = @"Line 1
@* Comment 1 *@
Line 2
@* Comment 2
still comment
*@
Line 3";
        var lexer = new Lexer(template);
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 1");
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 2");
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 3");
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("Comment"));
    }

    [Fact]
    public void Tokenize_SeparatorInsideMultiLineComment_IsIgnored()
    {
        var lexer = new Lexer("@*\n---\n===\n***\n*@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.DashSeparator);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.EqualsSeparator);
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.StarSeparator);
    }

    #endregion

    #region Comment lines should not produce empty lines

    [Fact]
    public void Tokenize_ConsecutiveCommentLines_ProduceNoNewLineTokens()
    {
        var lexer = new Lexer("@**@\n@**@\n@**@");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        // Should only have EOF token, no NewLine tokens for comment-only lines
        Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.NewLine);
    }

    [Fact]
    public void Tokenize_CommentLineBetweenContent_NoExtraNewLines()
    {
        var lexer = new Lexer("Line1\n@**@\nLine2");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        // Should have exactly 2 NewLine tokens (after Line1 and after Line2)
        var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
        Assert.Equal(2, newLineCount);
    }

    [Fact]
    public void Tokenize_MultipleConsecutiveCommentLines_NoNewLines()
    {
        var lexer = new Lexer("Header\n@* comment 1 *@\n@* comment 2 *@\n@* comment 3 *@\nFooter");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Header");
        Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Footer");
        // Should have exactly 2 NewLine tokens (after Header and after Footer)
        var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
        Assert.Equal(2, newLineCount);
    }

    [Fact]
    public void Tokenize_EmptyLinesBetweenContent_ProducesNewLines()
    {
        var lexer = new Lexer("Line1\n\n\nLine2");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        // Should have 4 NewLine tokens (after Line1, after each empty line, after Line2)
        var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
        Assert.Equal(4, newLineCount);
    }

    [Fact]
    public void Tokenize_EmptyLinesPreserved_CommentsRemoved()
    {
        var lexer = new Lexer("Header\n\n@* comment *@\n\nFooter");
        var result = lexer.Tokenize();

        Assert.False(result.HasErrors);
        // Should have 4 NewLine tokens: after Header, after first empty line, after second empty line, after Footer
        // The comment line should NOT produce a NewLine
        var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
        Assert.Equal(4, newLineCount);
    }

    #endregion
}
