using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Host.Engines;

namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;

/// <summary>
/// F8b — verifies <see cref="RoslynVirtualTagEvaluator"/> compiles user expressions through
/// the Core.Scripting sandbox, runs them against the dependency dictionary, caches the
/// compiled assembly per source, and surfaces failures (compile error, sandbox violation,
/// runtime throw) as <c>VirtualTagEvalResult.Failure</c> instead of propagating exceptions.
/// </summary>
public sealed class RoslynVirtualTagEvaluatorTests
{
    /// <summary>Verifies that simple addition expression is evaluated correctly.</summary>
    [Fact]
    public void Evaluate_simple_addition_returns_summed_value()
    {
        using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);

        var result = sut.Evaluate(
            virtualTagId: "vt-sum",
            expression: "return (int)ctx.GetTag(\"a\").Value + (int)ctx.GetTag(\"b\").Value;",
            dependencies: new Dictionary<string, object?> { ["a"] = 10, ["b"] = 32 });

        result.Success.ShouldBeTrue(result.Reason);
        result.Value.ShouldBe(42);
    }

    /// <summary>Verifies that compiled expressions are cached across multiple calls.</summary>
    [Fact]
    public void Evaluate_caches_compiled_expression_across_calls()
    {
        using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);
        const string expr = "return (int)ctx.GetTag(\"x\").Value * 2;";

        var first = sut.Evaluate("vt-cache", expr, new Dictionary<string, object?> { ["x"] = 5 });
        var second = sut.Evaluate("vt-cache", expr, new Dictionary<string, object?> { ["x"] = 7 });

        first.Success.ShouldBeTrue(first.Reason);
        first.Value.ShouldBe(10);
        second.Success.ShouldBeTrue(second.Reason);
        second.Value.ShouldBe(14);
    }

    /// <summary>Verifies that compile errors return Failure with a descriptive reason.</summary>
    [Fact]
    public void Evaluate_compile_error_returns_Failure_with_reason()
    {
        using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);

        var result = sut.Evaluate("vt-bad", "this is not valid C#;", new Dictionary<string, object?>());

        result.Success.ShouldBeFalse();
        result.Reason.ShouldNotBeNull();
        result.Reason.ShouldContain("compile");
    }

    /// <summary>Verifies that runtime exceptions return Failure with a descriptive reason.</summary>
    [Fact]
    public void Evaluate_runtime_exception_returns_Failure_with_reason()
    {
        using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);

        var result = sut.Evaluate(
            virtualTagId: "vt-div0",
            expression: "int a = 0; return 1 / a;",
            dependencies: new Dictionary<string, object?>());

        result.Success.ShouldBeFalse();
        result.Reason.ShouldNotBeNull();
        result.Reason.ShouldContain("threw");
    }

    /// <summary>Verifies that empty expressions return Failure.</summary>
    [Fact]
    public void Evaluate_empty_expression_returns_Failure()
    {
        using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);

        sut.Evaluate("vt-empty", "", new Dictionary<string, object?>()).Success.ShouldBeFalse();
        sut.Evaluate("vt-empty", "   ", new Dictionary<string, object?>()).Success.ShouldBeFalse();
    }

    /// <summary>Verifies that evaluation after disposal returns Failure.</summary>
    [Fact]
    public void Evaluate_after_dispose_returns_Failure()
    {
        var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);
        sut.Dispose();

        var result = sut.Evaluate("vt", "return 1;", new Dictionary<string, object?>());

        result.Success.ShouldBeFalse();
        result.Reason!.ShouldContain("disposed");
    }
}
