using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;

namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;

/// <summary>
/// Unit tests for <see cref="PassthroughScript"/> — the Roslyn-free classifier that recognises
/// the trivial mirror shape <c>return ctx.GetTag("X").Value;</c>. The pattern must be narrow:
/// any near-miss (arithmetic, a different member, multi-statement, a different method) falls
/// through so the Roslyn path stays authoritative.
/// </summary>
public sealed class PassthroughScriptTests
{
    /// <summary>The canonical mirror shape matches and captures the referenced tag.</summary>
    [Fact]
    public void Matches_canonical_mirror_and_captures_tag()
    {
        PassthroughScript.TryMatch("return ctx.GetTag(\"a\").Value;", out var tag).ShouldBeTrue();
        tag.ShouldBe("a");
    }

    /// <summary>A dotted / hierarchical tag reference is captured verbatim.</summary>
    [Fact]
    public void Captures_dotted_tag_reference()
    {
        PassthroughScript.TryMatch("return ctx.GetTag(\"Area1.Machine.Speed\").Value;", out var tag)
            .ShouldBeTrue();
        tag.ShouldBe("Area1.Machine.Speed");
    }

    /// <summary>Whitespace around every token is tolerated.</summary>
    [Fact]
    public void Tolerates_surrounding_and_inner_whitespace()
    {
        PassthroughScript.TryMatch("  return  ctx . GetTag( \"a\" ) . Value ; ", out var tag)
            .ShouldBeTrue();
        tag.ShouldBe("a");
    }

    /// <summary>Arithmetic on the mirror value is NOT a passthrough.</summary>
    [Fact]
    public void Rejects_arithmetic_on_value()
    {
        PassthroughScript.TryMatch("return (int)ctx.GetTag(\"a\").Value + 1;", out _).ShouldBeFalse();
    }

    /// <summary>Reading a different member (StatusCode) is NOT a passthrough.</summary>
    [Fact]
    public void Rejects_other_member_access()
    {
        PassthroughScript.TryMatch("return ctx.GetTag(\"a\").StatusCode;", out _).ShouldBeFalse();
    }

    /// <summary>A multi-statement body is NOT a passthrough.</summary>
    [Fact]
    public void Rejects_multi_statement_body()
    {
        PassthroughScript.TryMatch("var x = ctx.GetTag(\"a\").Value; return x;", out _).ShouldBeFalse();
    }

    /// <summary>A different method name is NOT a passthrough.</summary>
    [Fact]
    public void Rejects_different_method()
    {
        PassthroughScript.TryMatch("return ctx.GetVirtualTag(\"a\").Value;", out _).ShouldBeFalse();
    }

    /// <summary>Null / empty / whitespace input is rejected.</summary>
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Rejects_null_or_blank(string? source)
    {
        PassthroughScript.TryMatch(source, out var tag).ShouldBeFalse();
        tag.ShouldBe(string.Empty);
    }

    /// <summary>
    /// A tag literal containing a backslash escape (C# source <c>"a\\b"</c> → runtime name
    /// <c>a\b</c>) does NOT match the passthrough pattern — it falls through to Roslyn, which
    /// interprets the escape and resolves the correct dependency key.  Capturing the raw source
    /// text <c>a\\b</c> would produce a wrong-result silent miss against the key <c>a\b</c>.
    /// </summary>
    [Fact]
    public void Rejects_tag_literal_containing_backslash_escape()
    {
        // C# literal "return ctx.GetTag(\"a\\\\b\").Value;" → script source contains: a\\b (two chars: backslash + b)
        PassthroughScript.TryMatch("return ctx.GetTag(\"a\\\\b\").Value;", out var tag).ShouldBeFalse();
        tag.ShouldBe(string.Empty);
    }

    /// <summary>A plain dotted tag name (no backslash) still matches — the fix is additive only.</summary>
    [Fact]
    public void Matches_plain_dotted_tag_after_backslash_fix()
    {
        PassthroughScript.TryMatch("return ctx.GetTag(\"Site1.Area.Tag\").Value;", out var tag).ShouldBeTrue();
        tag.ShouldBe("Site1.Area.Tag");
    }
}
