using System.Collections.Generic;
using FluentAssertions;
using Workflow.Domain.Expressions;
using Xunit;

namespace Workflow.Tests.Condition;

public class ConditionEvaluatorTests
{
    private readonly ConditionEvaluator _sut = new();

    #region Simple Comparisons

    [Fact]
    public void Evaluate_Equals_ReturnsTrue_WhenValueMatches()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = "approved"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_Equals_ReturnsFalse_WhenValueDiffers()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = "rejected"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_NotEquals_ReturnsTrue_WhenValueDiffers()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "!=", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = "rejected"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_GreaterThan_ReturnsTrue_WhenValueIsGreater()
    {
        // Arrange
        const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
        var variables = new Dictionary<string, object>
        {
            ["amount"] = 7500
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_GreaterThan_ReturnsFalse_WhenValueIsLessOrEqual()
    {
        // Arrange
        const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
        var variables = new Dictionary<string, object>
        {
            ["amount"] = 5000
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_LessThan_ReturnsTrue_WhenValueIsLess()
    {
        // Arrange
        const string condition = """{ "field": "days", "op": "<", "value": 5 }""";
        var variables = new Dictionary<string, object>
        {
            ["days"] = 3
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_GreaterThanOrEqual_ReturnsTrue_WhenValueIsEqual()
    {
        // Arrange
        const string condition = """{ "field": "amount", "op": ">=", "value": 5000 }""";
        var variables = new Dictionary<string, object>
        {
            ["amount"] = 5000
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_LessThanOrEqual_ReturnsTrue_WhenValueIsEqual()
    {
        // Arrange
        const string condition = """{ "field": "days", "op": "<=", "value": 3 }""";
        var variables = new Dictionary<string, object>
        {
            ["days"] = 3
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_Contains_ReturnsTrue_WhenFieldContainsValue()
    {
        // Arrange
        const string condition = """{ "field": "description", "op": "contains", "value": "urgent" }""";
        var variables = new Dictionary<string, object>
        {
            ["description"] = "This is an urgent request"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_Contains_ReturnsFalse_WhenFieldDoesNotContainValue()
    {
        // Arrange
        const string condition = """{ "field": "description", "op": "contains", "value": "urgent" }""";
        var variables = new Dictionary<string, object>
        {
            ["description"] = "This is a routine request"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_In_ReturnsTrue_WhenValueInArray()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "in", "value": ["pending", "review", "approved"] }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = "review"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_In_ReturnsFalse_WhenValueNotInArray()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "in", "value": ["pending", "review", "approved"] }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = "rejected"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    #endregion

    #region Combined Conditions

    [Fact]
    public void Evaluate_And_ReturnsTrue_WhenAllConditionsTrue()
    {
        // Arrange
        const string condition = """
            { "and": [
                { "field": "days", "op": "<=", "value": 3 },
                { "field": "type", "op": "==", "value": "annual" }
            ]}
            """;
        var variables = new Dictionary<string, object>
        {
            ["days"] = 2,
            ["type"] = "annual"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_And_ReturnsFalse_WhenAnyConditionFalse()
    {
        // Arrange
        const string condition = """
            { "and": [
                { "field": "days", "op": "<=", "value": 3 },
                { "field": "type", "op": "==", "value": "annual" }
            ]}
            """;
        var variables = new Dictionary<string, object>
        {
            ["days"] = 5,
            ["type"] = "annual"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_Or_ReturnsTrue_WhenAnyConditionTrue()
    {
        // Arrange
        const string condition = """
            { "or": [
                { "field": "amount", "op": ">", "value": 10000 },
                { "field": "level", "op": "==", "value": "executive" }
            ]}
            """;
        var variables = new Dictionary<string, object>
        {
            ["amount"] = 5000,
            ["level"] = "executive"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_Or_ReturnsFalse_WhenAllConditionsFalse()
    {
        // Arrange
        const string condition = """
            { "or": [
                { "field": "amount", "op": ">", "value": 10000 },
                { "field": "level", "op": "==", "value": "executive" }
            ]}
            """;
        var variables = new Dictionary<string, object>
        {
            ["amount"] = 5000,
            ["level"] = "manager"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    #endregion

    #region Built-in Variables

    [Fact]
    public void Evaluate_LastResult_ReturnsApprovedResult()
    {
        // Arrange
        const string condition = """{ "field": "$lastResult", "op": "==", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["$lastResult"] = "approved"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_LastResult_WithRejectedResult()
    {
        // Arrange
        const string condition = """{ "field": "$lastResult", "op": "==", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["$lastResult"] = "rejected"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    #endregion

    #region Nested Conditions

    [Fact]
    public void Evaluate_NestedAndOr_CombinesCorrectly()
    {
        // Arrange
        // (type == "sick" OR type == "personal") AND (days <= 1)
        const string condition = """
            { "and": [
                { "or": [
                    { "field": "type", "op": "==", "value": "sick" },
                    { "field": "type", "op": "==", "value": "personal" }
                ]},
                { "field": "days", "op": "<=", "value": 1 }
            ]}
            """;
        var variables = new Dictionary<string, object>
        {
            ["type"] = "sick",
            ["days"] = 1
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    #endregion

    #region Edge Cases

    [Fact]
    public void Evaluate_MissingField_ReturnsFalse()
    {
        // Arrange
        const string condition = """{ "field": "nonexistent", "op": "==", "value": "something" }""";
        var variables = new Dictionary<string, object>
        {
            ["other"] = "value"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_NullVariable_ReturnsFalse()
    {
        // Arrange
        const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
        var variables = new Dictionary<string, object>
        {
            ["status"] = null!
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void Evaluate_EmptyCondition_ReturnsTrue()
    {
        // Arrange
        const string condition = "";
        var variables = new Dictionary<string, object>();

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_TypeMismatch_ReturnsFalse()
    {
        // Arrange
        const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
        var variables = new Dictionary<string, object>
        {
            ["amount"] = "not-a-number"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeFalse();
    }

    #endregion

    #region Numeric Comparisons

    [Fact]
    public void Evaluate_NumericGreaterThan_WithStringVariables_ParsesCorrectly()
    {
        // Arrange
        const string condition = """{ "field": "total", "op": ">", "value": 100 }""";
        var variables = new Dictionary<string, object>
        {
            ["total"] = "250"
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void Evaluate_NumericLessThan_WithDecimalValues()
    {
        // Arrange
        const string condition = """{ "field": "price", "op": "<", "value": 99.99 }""";
        var variables = new Dictionary<string, object>
        {
            ["price"] = 49.50
        };

        // Act
        var result = _sut.Evaluate(condition, variables);

        // Assert
        result.Should().BeTrue();
    }

    #endregion
}
