using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Types;

namespace ZB.MOM.WW.OtOpcUa.Commons.Tests;

public class EquipmentScriptPathsTests
{
    // ---- ExtractAlarmDependencyRefs (scripted-alarm dep graph; byte-parity seam) ----

    [Fact]
    public void ExtractAlarmDependencyRefs_predicate_reads_first_then_template_tokens_deduped()
    {
        // A template token identical to a predicate read appears once (predicate-first); a distinct
        // template token is appended after. This dedup/order is the parity contract both the composer
        // and the artifact decode rely on.
        EquipmentScriptPaths
            .ExtractAlarmDependencyRefs(
                predicateSource: "return ctx.GetTag(\"Mach.Temp\").Value > 90;",
                messageTemplate: "Temp {Mach.Temp} over {Mach.Limit}")
            .ShouldBe(["Mach.Temp", "Mach.Limit"]);
    }

    [Fact]
    public void ExtractAlarmDependencyRefs_excludes_reserved_equip_double_brace_token()
    {
        // The reserved {{equip}} double-brace form must NOT be picked up as a single-brace {TagPath}
        // token; only the genuine {Line.Temp} token is extracted.
        EquipmentScriptPaths
            .ExtractAlarmDependencyRefs(
                predicateSource: null,
                messageTemplate: "{{equip}} too hot: {Line.Temp}")
            .ShouldBe(["Line.Temp"]);
    }

    // ---- DeriveEquipmentBase ----

    [Fact]
    public void DeriveEquipmentBase_returns_shared_prefix()
    {
        EquipmentScriptPaths
            .DeriveEquipmentBase(["TestMachine_001.A", "TestMachine_001.B"])
            .ShouldBe("TestMachine_001");
    }

    [Fact]
    public void DeriveEquipmentBase_returns_null_when_prefixes_diverge()
    {
        EquipmentScriptPaths
            .DeriveEquipmentBase(["TestMachine_001.A", "DelmiaReceiver_001.B"])
            .ShouldBeNull();
    }

    [Fact]
    public void DeriveEquipmentBase_returns_null_for_empty_sequence()
    {
        EquipmentScriptPaths
            .DeriveEquipmentBase([])
            .ShouldBeNull();
    }

    [Fact]
    public void DeriveEquipmentBase_returns_whole_value_when_no_dot()
    {
        EquipmentScriptPaths
            .DeriveEquipmentBase(["NoDot"])
            .ShouldBe("NoDot");
    }

    [Fact]
    public void DeriveEquipmentBase_skips_null_empty_and_whitespace_entries()
    {
        EquipmentScriptPaths
            .DeriveEquipmentBase([null, "", "  ", "TestMachine_001.A"])
            .ShouldBe("TestMachine_001");
    }

    // ---- SubstituteEquipmentToken ----

    [Fact]
    public void SubstituteEquipmentToken_substitutes_inside_GetTag()
    {
        var result = EquipmentScriptPaths.SubstituteEquipmentToken(
            "ctx.GetTag(\"{{equip}}.Source\")", "TestMachine_001");

        result.ShouldContain("ctx.GetTag(\"TestMachine_001.Source\")");
    }

    [Fact]
    public void SubstituteEquipmentToken_substitutes_inside_SetVirtualTag()
    {
        var result = EquipmentScriptPaths.SubstituteEquipmentToken(
            "ctx.SetVirtualTag(\"{{equip}}.Out\", x)", "TestMachine_001");

        result.ShouldContain("ctx.SetVirtualTag(\"TestMachine_001.Out\"");
    }

    [Fact]
    public void SubstituteEquipmentToken_leaves_comment_unchanged()
    {
        var source = "// uses {{equip}} here";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
            .ShouldBe(source);
    }

    [Fact]
    public void SubstituteEquipmentToken_leaves_logger_string_unchanged()
    {
        var source = "ctx.Logger.Information(\"{{equip}}\")";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
            .ShouldBe(source);
    }

    [Fact]
    public void SubstituteEquipmentToken_substitutes_all_occurrences()
    {
        var source = "ctx.GetTag(\"{{equip}}.A\"); ctx.GetTag(\"{{equip}}.B\");";

        var result = EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001");

        result.ShouldContain("ctx.GetTag(\"TestMachine_001.A\")");
        result.ShouldContain("ctx.GetTag(\"TestMachine_001.B\")");
        result.ShouldNotContain(EquipmentScriptPaths.EquipToken);
    }

    [Fact]
    public void SubstituteEquipmentToken_is_identity_when_equipBase_is_null()
    {
        var source = "ctx.GetTag(\"{{equip}}.Source\")";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, null)
            .ShouldBe(source);
    }

    [Fact]
    public void SubstituteEquipmentToken_is_identity_when_equipBase_is_empty()
    {
        var source = "ctx.GetTag(\"{{equip}}.Source\")";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, "")
            .ShouldBe(source);
    }

    [Fact]
    public void SubstituteEquipmentToken_is_identity_when_token_absent()
    {
        var source = "ctx.GetTag(\"TestMachine_001.Source\")";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
            .ShouldBe(source);
    }

    [Fact]
    public void SubstituteEquipmentToken_does_not_touch_raw_string_literal()
    {
        // Documents the regex limitation: only "double-quote" literals are handled,
        // matching the existing seam extractors. A raw-string literal is left as-is.
        var source = "ctx.GetTag(\"\"\"{{equip}}.X\"\"\")";

        EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
            .ShouldBe(source);
    }

    // ---- ExtractDependencyRefs ----

    [Fact]
    public void ExtractDependencyRefs_returns_distinct_refs_in_first_seen_order()
    {
        var source = "ctx.GetTag(\"A\"); ctx.GetTag(\"B\"); ctx.GetTag(\"A\");";

        EquipmentScriptPaths.ExtractDependencyRefs(source)
            .ShouldBe(["A", "B"]);
    }

    [Fact]
    public void ExtractDependencyRefs_excludes_SetVirtualTag_writes()
    {
        var source = "ctx.GetTag(\"X\"); ctx.SetVirtualTag(\"Y\", 1);";

        EquipmentScriptPaths.ExtractDependencyRefs(source)
            .ShouldBe(["X"]);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void ExtractDependencyRefs_returns_empty_for_null_or_whitespace(string? source)
    {
        EquipmentScriptPaths.ExtractDependencyRefs(source).ShouldBeEmpty();
    }

    // ---- ContainsEquipToken ----

    [Fact]
    public void ContainsEquipToken_true_when_token_present()
    {
        EquipmentScriptPaths.ContainsEquipToken("ctx.GetTag(\"{{equip}}.X\")").ShouldBeTrue();
    }

    [Fact]
    public void ContainsEquipToken_false_when_token_absent()
    {
        EquipmentScriptPaths.ContainsEquipToken("ctx.GetTag(\"X\")").ShouldBeFalse();
    }

    [Fact]
    public void ContainsEquipToken_false_for_null()
    {
        EquipmentScriptPaths.ContainsEquipToken(null).ShouldBeFalse();
    }
}
