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

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

public sealed class DiagnoseTests
{
    private static readonly ScriptAnalysisService Svc = new();
    private static IReadOnlyList<DiagnosticMarker> Diag(string code) => Svc.Diagnose(new DiagnoseRequest(code)).Markers;

    [Fact] public void Clean_script_has_no_markers()
        => Diag("""return (double)ctx.GetTag("A").Value * 2.0;""").ShouldBeEmpty();

    [Fact] public void Roslyn_error_is_reported_on_user_line_1()
    {
        var m = Diag("return ctx.NoSuchMember();");
        m.ShouldNotBeEmpty();
        m.ShouldContain(x => x.Severity == 8);
        m.First(x => x.Severity == 8).StartLineNumber.ShouldBe(1);   // #line 1 → user coords (GetMappedLineSpan)
    }

    [Fact] public void Roslyn_error_maps_to_correct_user_line()
    {
        // error on the THIRD user line — proves mapping isn't off by the preamble height
        var m = Diag("var a = 1;\nvar b = 2;\nreturn ctx.Nope();");
        m.ShouldContain(x => x.Severity == 8 && x.StartLineNumber == 3);
    }

    [Fact] public void Forbidden_or_unresolved_dangerous_type_is_flagged()
        => Diag("""var c = new System.Net.Http.HttpClient(); return 0;""")
            .ShouldContain(x => x.Severity == 8);

    [Fact] public void Dynamic_tag_path_is_flagged()
        => Diag("""var p = "A"; return ctx.GetTag(p).Value;""")
            .ShouldContain(x => x.Message.Contains("literal"));

    [Fact] public void Missing_return_does_not_produce_a_phantom_preamble_marker()
        => Diag("var x = 1;").ShouldNotContain(m => m.Code == "CS0161");

    [Fact] public void Empty_code_has_no_markers()
        => Diag("").ShouldBeEmpty();

    [Fact] public void Crlf_source_maps_dynamic_path_to_the_correct_line()
    {
        // \r\n line endings; the dynamic-path rejection must land on user line 3, not be column-shifted.
        var m = Diag("var a = 1;\r\nvar b = 2;\r\nreturn ctx.GetTag(b > 0 ? \"x\" : \"y\").Value;");
        m.ShouldContain(x => x.Message.Contains("literal") && x.StartLineNumber == 3);
    }
}
