using Inspectron.Epson.TemplateEngine.Parsing;

namespace Inspectron.Epson.TemplateEngine.Tests;

public class TemplateParserTests
{
    private readonly TemplateParser _parser = new();

    [Fact]
    public void Parse_EmptyReceipt()
    {
        var result = _parser.Parse("<receipt></receipt>");
        Assert.NotNull(result);
        Assert.Empty(result.Children);
    }

    [Fact]
    public void Parse_InvalidXml_Throws()
    {
        Assert.Throws<TemplateParsingException>(() => _parser.Parse("not xml"));
    }

    [Fact]
    public void Parse_WrongRoot_Throws()
    {
        Assert.Throws<TemplateParsingException>(() => _parser.Parse("<div></div>"));
    }

    [Fact]
    public void Parse_UnknownElement_Throws()
    {
        Assert.Throws<TemplateParsingException>(() =>
            _parser.Parse("<receipt><unknown /></receipt>"));
    }

    [Fact]
    public void Parse_Line_BasicText()
    {
        var result = _parser.Parse("<receipt><line>Hello</line></receipt>");
        Assert.Single(result.Children);
        var line = Assert.IsType<LineNode>(result.Children[0]);
        Assert.Equal("Hello", line.Text);
    }

    [Fact]
    public void Parse_Line_WithFormatting()
    {
        var result = _parser.Parse(
            """<receipt><line bold="true" big="true" tall="true" red="true" align="center">Text</line></receipt>""");
        var line = Assert.IsType<LineNode>(result.Children[0]);
        Assert.True(line.Bold);
        Assert.True(line.Big);
        Assert.True(line.Tall);
        Assert.True(line.Red);
        Assert.Equal("center", line.Align);
    }

    [Fact]
    public void Parse_Line_WithWrap()
    {
        var result = _parser.Parse(
            """<receipt><line wrap="true" wrapIndent="4">Long text</line></receipt>""");
        var line = Assert.IsType<LineNode>(result.Children[0]);
        Assert.True(line.Wrap);
        Assert.Equal(4, line.WrapIndent);
    }

    [Fact]
    public void Parse_Line_WithLineSpacing()
    {
        var result = _parser.Parse(
            """<receipt><line lineSpacing="50">Text</line></receipt>""");
        var line = Assert.IsType<LineNode>(result.Children[0]);
        Assert.Equal(50, line.LineSpacing);
    }

    [Fact]
    public void Parse_EmptyLine()
    {
        var result = _parser.Parse("<receipt><line /></receipt>");
        var line = Assert.IsType<LineNode>(result.Children[0]);
        Assert.Equal("", line.Text);
    }

    [Fact]
    public void Parse_Columns()
    {
        var result = _parser.Parse(
            """<receipt><columns left="Name" right="Price" bold="true" /></receipt>""");
        var col = Assert.IsType<ColumnsNode>(result.Children[0]);
        Assert.Equal("Name", col.Left);
        Assert.Equal("Price", col.Right);
        Assert.True(col.Bold);
    }

    [Fact]
    public void Parse_Row_WithColumns()
    {
        var result = _parser.Parse("""
            <receipt>
              <row bold="true">
                <col width="10" align="left">Name</col>
                <col width="10" align="right">Value</col>
              </row>
            </receipt>
            """);

        var row = Assert.IsType<RowNode>(result.Children[0]);
        Assert.True(row.Bold);
        Assert.Equal(2, row.Columns.Count);
        Assert.Equal("Name", row.Columns[0].Text);
        Assert.Equal(10, row.Columns[0].Width);
        Assert.Equal("left", row.Columns[0].Align);
        Assert.Equal("Value", row.Columns[1].Text);
        Assert.Equal(10, row.Columns[1].Width);
        Assert.Equal("right", row.Columns[1].Align);
    }

    [Fact]
    public void Parse_Separator()
    {
        var result = _parser.Parse("<receipt><separator /></receipt>");
        var sep = Assert.IsType<SeparatorNode>(result.Children[0]);
        Assert.Equal('-', sep.Character);
    }

    [Fact]
    public void Parse_Separator_CustomChar()
    {
        var result = _parser.Parse("""<receipt><separator char="*" /></receipt>""");
        var sep = Assert.IsType<SeparatorNode>(result.Children[0]);
        Assert.Equal('*', sep.Character);
    }

    [Fact]
    public void Parse_Cut()
    {
        var result = _parser.Parse("<receipt><cut /></receipt>");
        Assert.IsType<CutNode>(result.Children[0]);
    }

    [Fact]
    public void Parse_Feed()
    {
        var result = _parser.Parse("""<receipt><feed lines="3" /></receipt>""");
        var feed = Assert.IsType<FeedNode>(result.Children[0]);
        Assert.Equal(3, feed.Lines);
    }

    [Fact]
    public void Parse_Feed_Default()
    {
        var result = _parser.Parse("<receipt><feed /></receipt>");
        var feed = Assert.IsType<FeedNode>(result.Children[0]);
        Assert.Equal(1, feed.Lines);
    }

    [Fact]
    public void Parse_Foreach()
    {
        var result = _parser.Parse("""
            <receipt>
              <foreach items="Items" var="item">
                <line>{{item.Name}}</line>
              </foreach>
            </receipt>
            """);

        var fe = Assert.IsType<ForeachNode>(result.Children[0]);
        Assert.Equal("Items", fe.Items);
        Assert.Equal("item", fe.Var);
        Assert.Single(fe.Children);
        Assert.IsType<LineNode>(fe.Children[0]);
    }

    [Fact]
    public void Parse_Foreach_MissingItems_Throws()
    {
        Assert.Throws<TemplateParsingException>(() =>
            _parser.Parse("""<receipt><foreach var="item"><line /></foreach></receipt>"""));
    }

    [Fact]
    public void Parse_Foreach_MissingVar_Throws()
    {
        Assert.Throws<TemplateParsingException>(() =>
            _parser.Parse("""<receipt><foreach items="Items"><line /></foreach></receipt>"""));
    }

    [Fact]
    public void Parse_If()
    {
        var result = _parser.Parse("""
            <receipt>
              <if test="Name">
                <line>Has name</line>
              </if>
            </receipt>
            """);

        var ifNode = Assert.IsType<IfNode>(result.Children[0]);
        Assert.Equal("Name", ifNode.Test);
        Assert.Single(ifNode.Children);
    }

    [Fact]
    public void Parse_If_MissingTest_Throws()
    {
        Assert.Throws<TemplateParsingException>(() =>
            _parser.Parse("<receipt><if><line /></if></receipt>"));
    }

    [Fact]
    public void Parse_IfElse()
    {
        var result = _parser.Parse("""
            <receipt>
              <if test="Name">
                <line>Yes</line>
              </if>
              <else>
                <line>No</line>
              </else>
            </receipt>
            """);

        Assert.Equal(2, result.Children.Count);
        Assert.IsType<IfNode>(result.Children[0]);
        Assert.IsType<ElseNode>(result.Children[1]);
    }

    [Fact]
    public void Parse_NestedForeach()
    {
        var result = _parser.Parse("""
            <receipt>
              <foreach items="Gangs" var="gang">
                <foreach items="gang.Dishes" var="dish">
                  <line>{{dish.Name}}</line>
                </foreach>
              </foreach>
            </receipt>
            """);

        var outer = Assert.IsType<ForeachNode>(result.Children[0]);
        Assert.Equal("Gangs", outer.Items);
        var inner = Assert.IsType<ForeachNode>(outer.Children[0]);
        Assert.Equal("gang.Dishes", inner.Items);
    }

    [Fact]
    public void Parse_Table()
    {
        var result = _parser.Parse("""
            <receipt>
              <table items="Data" var="row">
                <col width="10" align="left">Header1</col>
                <col width="10" align="right">Header2</col>
              </table>
            </receipt>
            """);

        var table = Assert.IsType<TableNode>(result.Children[0]);
        Assert.Equal("Data", table.Items);
        Assert.Equal("row", table.Var);
        Assert.Equal(2, table.Columns.Count);
    }

    [Fact]
    public void Parse_ComplexTemplate()
    {
        var result = _parser.Parse("""
            <receipt>
              <line align="center" big="true">Title</line>
              <separator />
              <line />
              <foreach items="Items" var="item">
                <line bold="true">{{item.Name}}</line>
                <if test="item.HasDiscount">
                  <line red="true">DISCOUNT</line>
                </if>
              </foreach>
              <cut />
            </receipt>
            """);

        Assert.Equal(5, result.Children.Count);
        Assert.IsType<LineNode>(result.Children[0]);
        Assert.IsType<SeparatorNode>(result.Children[1]);
        Assert.IsType<LineNode>(result.Children[2]);
        Assert.IsType<ForeachNode>(result.Children[3]);
        Assert.IsType<CutNode>(result.Children[4]);
    }
}
