using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis;

public sealed class HoverSignatureTests
{
    private static readonly ScriptAnalysisService Svc = new();

    /// <summary>A fake catalog that resolves only "Line1.Speed" (Tag/Double/driver) and nothing else,
    /// so the tag-path hover branch can be exercised without a database.</summary>
    private sealed class FakeCatalog : IScriptTagCatalog
    {
        public Task<IReadOnlyList<string>> GetPathsAsync(string? f, CancellationToken ct)
            => Task.FromResult<IReadOnlyList<string>>(System.Array.Empty<string>());
        public Task<ScriptTagInfo?> GetTagInfoAsync(string path, CancellationToken ct)
            => Task.FromResult(path == "Line1.Speed" ? new ScriptTagInfo("Line1.Speed", "Tag", "Double", "MAIN-modbus") : null);
        public Task<IReadOnlyList<string>> GetEquipmentReferenceNamesAsync(string equipmentId, string? filter, CancellationToken ct)
            => Task.FromResult<IReadOnlyList<string>>(new[] { "Speed" });
    }

    [Fact] public async Task Hover_on_GetTag_returns_member_markdown()
    {
        // hover over the "GetTag" identifier in ctx.GetTag("A")
        var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("A").Value;""", 1, 16))).Markdown;
        md.ShouldNotBeNull();
        md!.ShouldContain("GetTag");
    }

    [Fact] public async Task Hover_on_nothing_returns_null()
        => (await Svc.Hover(new HoverRequest("   ", 1, 1))).Markdown.ShouldBeNull();

    [Fact] public void SignatureHelp_inside_GetTag_call_shows_the_signature()
    {
        // caret just after the open paren of ctx.GetTag(
        var sh = Svc.SignatureHelp(new SignatureHelpRequest("""return ctx.GetTag();""", 1, 19));
        sh.Label.ShouldNotBeNull();
        sh.Label!.ShouldContain("GetTag");
        sh.Parameters.ShouldNotBeNull();
        sh.Parameters!.ShouldContain(p => p.Label.Contains("path") || p.Label.Contains("string"));
    }

    [Fact] public async Task Hover_on_a_plain_local_resolves_the_local_not_an_enclosing_member()
    {
        // hovering the local `x` (not a member) must resolve the LOCAL, and must NOT climb to GetTag/Value.
        var md = (await Svc.Hover(new HoverRequest("var x = 1;\nreturn ctx.GetTag(\"A\").Value + x;", 2, 33))).Markdown;
        md.ShouldNotBeNull();
        md!.ShouldContain("x");
        md.ShouldNotContain("GetTag");
    }

    [Fact] public void SignatureHelp_for_parameterless_call_does_not_throw_and_clamps_active_to_zero()
    {
        // ctx.Now is a property, not a call; use a parameterless method if one exists, else assert empty-safe behavior.
        // Deadband is static on ScriptContext (3 params) — instead verify a 0-arg edge via an empty arg list doesn't throw:
        var sh = Svc.SignatureHelp(new SignatureHelpRequest("return ctx.GetTag();", 1, 19));
        sh.ActiveParameter.ShouldBe(0);   // clamped; no exception
    }

    [Fact] public async Task Hover_with_out_of_range_position_returns_null_without_throwing()
        => (await Svc.Hover(new HoverRequest("return 1;", 99, 99))).Markdown.ShouldBeNull();

    // ── tag-path hover (driven by the catalog) ─────────────────────────────────────────────────

    [Fact] public async Task Hover_on_known_tag_path_literal_shows_kind_type_and_path()
    {
        var svc = new ScriptAnalysisService(new FakeCatalog());
        // caret on the path literal inside ctx.GetTag("Line1.Speed")
        var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Line1.Speed").Value;""", 1, 24))).Markdown;
        md.ShouldNotBeNull();
        md!.ShouldContain("Line1.Speed");
        md.ShouldContain("Double");
        md.ShouldContain("Tag");
        md.ShouldContain("MAIN-modbus");
    }

    [Fact] public async Task Hover_on_unknown_tag_path_literal_reports_not_known()
    {
        var svc = new ScriptAnalysisService(new FakeCatalog());
        // caret on the path literal inside ctx.GetTag("Unknownz")
        var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Unknownz").Value;""", 1, 22))).Markdown;
        md.ShouldNotBeNull();
        md!.ShouldContain("Not a known");
    }
}
