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

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

/// <summary>
/// F9b — verifies <see cref="RoslynScriptedAlarmEvaluator"/> compiles alarm predicates,
/// returns the bool result on success, surfaces compile/runtime errors as Failure (so the
/// actor preserves prior state), and rejects predicates that try to ctx.SetVirtualTag (the
/// AlarmPredicateContext throws on writes — predicates must stay pure).
/// </summary>
public sealed class RoslynScriptedAlarmEvaluatorTests
{
    /// <summary>Verifies evaluation of predicate returning true reports Active.</summary>
    [Fact]
    public void Evaluate_predicate_returning_true_reports_Active()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);

        var result = sut.Evaluate(
            alarmId: "alarm-hi",
            predicate: "return (int)ctx.GetTag(\"temp\").Value > 100;",
            dependencies: new Dictionary<string, object?> { ["temp"] = 150 });

        result.Success.ShouldBeTrue(result.Reason);
        result.Active.ShouldBeTrue();
    }

    /// <summary>Verifies evaluation of predicate returning false reports Inactive.</summary>
    [Fact]
    public void Evaluate_predicate_returning_false_reports_Inactive()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);

        var result = sut.Evaluate(
            alarmId: "alarm-hi",
            predicate: "return (int)ctx.GetTag(\"temp\").Value > 100;",
            dependencies: new Dictionary<string, object?> { ["temp"] = 50 });

        result.Success.ShouldBeTrue(result.Reason);
        result.Active.ShouldBeFalse();
    }

    /// <summary>Verifies compiled predicates are cached across calls.</summary>
    [Fact]
    public void Evaluate_caches_compiled_predicate_across_calls()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
        const string predicate = "return (bool)ctx.GetTag(\"door_open\").Value;";

        var first = sut.Evaluate("alarm-door", predicate, new Dictionary<string, object?> { ["door_open"] = true });
        var second = sut.Evaluate("alarm-door", predicate, new Dictionary<string, object?> { ["door_open"] = false });

        first.Active.ShouldBeTrue();
        second.Active.ShouldBeFalse();
    }

    /// <summary>Verifies compile errors return Failure.</summary>
    [Fact]
    public void Evaluate_compile_error_returns_Failure()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);

        var result = sut.Evaluate("alarm-bad", "this isn't C#;", new Dictionary<string, object?>());

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

    /// <summary>Verifies predicate writing virtual tag returns Failure.</summary>
    [Fact]
    public void Evaluate_predicate_writing_virtual_tag_returns_Failure()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);

        // AlarmPredicateContext.SetVirtualTag throws — wrapper catches + reports as Failure.
        var result = sut.Evaluate(
            alarmId: "alarm-bad-write",
            predicate: "ctx.SetVirtualTag(\"x\", 1); return true;",
            dependencies: new Dictionary<string, object?>());

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

    /// <summary>Verifies empty predicate returns Failure.</summary>
    [Fact]
    public void Evaluate_empty_predicate_returns_Failure()
    {
        using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);

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

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

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

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