using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Validation;

public class ScriptCompilerTests
{
    private readonly ScriptCompiler _sut = new();

    [Fact]
    public void TryCompile_ValidCode_ReturnsSuccess()
    {
        var result = _sut.TryCompile("var x = 1; if (x > 0) { x++; }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_EmptyCode_ReturnsFailure()
    {
        var result = _sut.TryCompile("", "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("empty", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void TryCompile_MismatchedBraces_ReturnsFailure()
    {
        var result = _sut.TryCompile("if (true) { x = 1;", "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("braces", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void TryCompile_UnclosedBlockComment_ReturnsFailure()
    {
        var result = _sut.TryCompile("/* this is never closed", "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("comment", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Theory]
    [InlineData("System.IO.File.ReadAllText(\"x\");")]
    [InlineData("System.Diagnostics.Process.Start(\"cmd\");")]
    [InlineData("System.Threading.Thread.Sleep(1000);")]
    [InlineData("System.Reflection.Assembly.Load(\"x\");")]
    [InlineData("System.Net.Sockets.TcpClient c;")]
    [InlineData("System.Net.Http.HttpClient c;")]
    public void TryCompile_ForbiddenApi_ReturnsFailure(string code)
    {
        var result = _sut.TryCompile(code, "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("forbidden", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void TryCompile_BracesInStrings_Ignored()
    {
        var result = _sut.TryCompile("var s = \"{ not a brace }\";", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_BracesInComments_Ignored()
    {
        var result = _sut.TryCompile("// { not a brace\nvar x = 1;", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_BlockCommentWithBraces_Ignored()
    {
        var result = _sut.TryCompile("/* { } */ var x = 1;", "Test");
        Assert.True(result.IsSuccess);
    }

    // --- TemplateEngine-007 regression: string-literal awareness ---

    [Fact]
    public void TryCompile_VerbatimStringWithBrace_NotFlaggedAsMismatched()
    {
        // @"..." — backslash is literal, "" is the escape. The closing brace
        // inside the verbatim string must not affect the brace balance.
        var result = _sut.TryCompile("var s = @\"a brace } and a \\ slash\"; if (true) { }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_VerbatimStringWithEscapedQuote_NotFlaggedAsMismatched()
    {
        // The "" inside a verbatim string is an escaped quote, not a string end.
        var result = _sut.TryCompile("var s = @\"he said \"\"hi}\"\"\"; { }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_InterpolatedStringWithBraces_NotFlaggedAsMismatched()
    {
        // The braces in $"{x}" are interpolation holes; the literal "}}" is an
        // escaped brace. Neither should unbalance the real braces.
        var result = _sut.TryCompile("var x = 1; var s = $\"val={x} literal}}\"; if (x>0) { x++; }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_RawStringLiteralWithBraces_NotFlaggedAsMismatched()
    {
        // C# 11 raw string literal — the triple quotes delimit, braces inside are text.
        var result = _sut.TryCompile("var s = \"\"\"a } brace { in raw\"\"\"; { }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_CharLiteralWithBrace_NotFlaggedAsMismatched()
    {
        // A '}' char literal must not decrement the brace depth.
        var result = _sut.TryCompile("var c = '}'; if (true) { }", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_GenuineMismatchedBraces_StillDetected()
    {
        // Sanity check that the string-aware scan still catches real mismatches.
        var result = _sut.TryCompile("var s = \"ok\"; if (true) { x++;", "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("braces", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    // --- TemplateEngine-006 regression: forbidden-API scan false positives ---

    [Fact]
    public void TryCompile_ForbiddenApiTextInsideStringLiteral_NotFlagged()
    {
        // "System.IO." appears only inside a string literal — it is inert text,
        // not a use of the forbidden API, and must not be rejected.
        var result = _sut.TryCompile("var msg = \"see System.IO.File docs\"; var x = 1;", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_ForbiddenApiTextInsideComment_NotFlagged()
    {
        // "System.Threading." appears only inside a comment — inert.
        var result = _sut.TryCompile("// avoid System.Threading.Thread here\nvar x = 1;", "Test");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void TryCompile_ForbiddenApiInRealCode_StillFlagged()
    {
        // Sanity check: a genuine use in code is still rejected.
        var result = _sut.TryCompile("var x = System.IO.File.ReadAllText(\"a\");", "Test");
        Assert.True(result.IsFailure);
        Assert.Contains("forbidden", result.Error, StringComparison.OrdinalIgnoreCase);
    }
}
