using System.Text.Json;
using Inspectron.Epson.TemplateEngine.DataBinding;

namespace Inspectron.Epson.TemplateEngine.Tests;

public class ExpressionEvaluatorTests
{
    private static DataContext CreateContext(string json)
    {
        var root = JsonDocument.Parse(json).RootElement;
        return new DataContext(root);
    }

    [Fact]
    public void Evaluate_SimpleProperty()
    {
        var ctx = CreateContext("""{"Name":"John"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Hello John", eval.Evaluate("Hello {{Name}}"));
    }

    [Fact]
    public void Evaluate_NestedProperty()
    {
        var ctx = CreateContext("""{"Person":{"Name":"Alice"}}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Hi Alice", eval.Evaluate("Hi {{Person.Name}}"));
    }

    [Fact]
    public void Evaluate_MissingProperty_ReturnsEmpty()
    {
        var ctx = CreateContext("""{"Name":"John"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Hi ", eval.Evaluate("Hi {{Missing}}"));
    }

    [Fact]
    public void Evaluate_NumberProperty()
    {
        var ctx = CreateContext("""{"Count":42}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Count: 42", eval.Evaluate("Count: {{Count}}"));
    }

    [Fact]
    public void Evaluate_FormatString_Decimal()
    {
        var ctx = CreateContext("""{"Price":9.5}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("9.50", eval.Evaluate("{{Price:F2}}"));
    }

    [Fact]
    public void Evaluate_FormatString_DateTime()
    {
        var ctx = CreateContext("""{"Date":"2024-03-15T14:30:00Z"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("15-Mar-24 14:30", eval.Evaluate("{{Date:dd-MMM-yy HH:mm}}"));
    }

    [Fact]
    public void Evaluate_MultipleExpressions()
    {
        var ctx = CreateContext("""{"First":"John","Last":"Doe"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("John Doe", eval.Evaluate("{{First}} {{Last}}"));
    }

    [Fact]
    public void Evaluate_CaseInsensitivePropertyLookup()
    {
        var ctx = CreateContext("""{"name":"Alice"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Alice", eval.Evaluate("{{Name}}"));
    }

    [Fact]
    public void Evaluate_EmptyTemplate()
    {
        var ctx = CreateContext("""{}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("", eval.Evaluate(""));
    }

    [Fact]
    public void Evaluate_NoExpressions()
    {
        var ctx = CreateContext("""{}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("plain text", eval.Evaluate("plain text"));
    }

    [Fact]
    public void Evaluate_LoopVariable_Index()
    {
        var ctx = CreateContext("""{}""");
        ctx.SetLoopVariable("$index", 3);
        var eval = new ExpressionEvaluator(ctx);

        Assert.Equal("Index: 3", eval.Evaluate("Index: {{$index}}"));
    }

    [Fact]
    public void EvaluateCondition_TruthyString()
    {
        var ctx = CreateContext("""{"Name":"John"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Name"));
    }

    [Fact]
    public void EvaluateCondition_EmptyString_IsFalsy()
    {
        var ctx = CreateContext("""{"Name":""}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("Name"));
    }

    [Fact]
    public void EvaluateCondition_MissingProperty_IsFalsy()
    {
        var ctx = CreateContext("""{}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("Name"));
    }

    [Fact]
    public void EvaluateCondition_Negation()
    {
        var ctx = CreateContext("""{"Name":"John"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("!Name"));
    }

    [Fact]
    public void EvaluateCondition_Negation_Missing()
    {
        var ctx = CreateContext("""{}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("!Name"));
    }

    [Fact]
    public void EvaluateCondition_NullValue_IsFalsy()
    {
        var ctx = CreateContext("""{"Name":null}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("Name"));
    }

    [Fact]
    public void EvaluateCondition_Comparison_Equal()
    {
        var ctx = CreateContext("""{"Count":5}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Count==5"));
    }

    [Fact]
    public void EvaluateCondition_Comparison_NotEqual()
    {
        var ctx = CreateContext("""{"Count":5}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Count!=3"));
    }

    [Fact]
    public void EvaluateCondition_Comparison_GreaterThan()
    {
        var ctx = CreateContext("""{"Count":5}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Count>3"));
    }

    [Fact]
    public void EvaluateCondition_Comparison_StringEqual()
    {
        var ctx = CreateContext("""{"Status":"active"}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Status=='active'"));
    }

    [Fact]
    public void EvaluateCondition_LoopVariable_First()
    {
        var ctx = CreateContext("""{}""");
        ctx.SetLoopVariable("$first", true);
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("$first"));
    }

    [Fact]
    public void EvaluateCondition_LoopVariable_NotLast()
    {
        var ctx = CreateContext("""{}""");
        ctx.SetLoopVariable("$last", false);
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("!$last"));
    }

    [Fact]
    public void EvaluateCondition_NonEmptyArray_IsTruthy()
    {
        var ctx = CreateContext("""{"Items":[1,2,3]}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Items"));
    }

    [Fact]
    public void EvaluateCondition_EmptyArray_IsFalsy()
    {
        var ctx = CreateContext("""{"Items":[]}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("Items"));
    }

    [Fact]
    public void EvaluateCondition_BooleanTrue_IsTruthy()
    {
        var ctx = CreateContext("""{"Active":true}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.True(eval.EvaluateCondition("Active"));
    }

    [Fact]
    public void EvaluateCondition_BooleanFalse_IsFalsy()
    {
        var ctx = CreateContext("""{"Active":false}""");
        var eval = new ExpressionEvaluator(ctx);

        Assert.False(eval.EvaluateCondition("Active"));
    }

    [Fact]
    public void DataContext_ChildScope_InheritsParentVariables()
    {
        var ctx = CreateContext("""{}""");
        var item = JsonDocument.Parse("""{"Name":"Pizza"}""").RootElement;
        ctx.SetVariable("item", item);

        var child = ctx.CreateChildScope();
        var childEval = new ExpressionEvaluator(child);

        Assert.Equal("Pizza", childEval.Evaluate("{{item.Name}}"));
    }

    [Fact]
    public void DataContext_ChildScope_OverridesParentVariable()
    {
        var ctx = CreateContext("""{}""");
        var item1 = JsonDocument.Parse("""{"Name":"Pizza"}""").RootElement;
        ctx.SetVariable("item", item1);

        var child = ctx.CreateChildScope();
        var item2 = JsonDocument.Parse("""{"Name":"Pasta"}""").RootElement;
        child.SetVariable("item", item2);

        var childEval = new ExpressionEvaluator(child);
        Assert.Equal("Pasta", childEval.Evaluate("{{item.Name}}"));

        var parentEval = new ExpressionEvaluator(ctx);
        Assert.Equal("Pizza", parentEval.Evaluate("{{item.Name}}"));
    }
}
