using ZB.MOM.WW.ScadaBridge.Commons.Types;

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

/// <summary>
/// Validates script code by attempting to compile it using Roslyn.
/// In production, this would compile C# scripts against a stub ScriptApi assembly
/// that provides the allowed API surface (attribute read/write, CallScript, CallShared, etc.)
/// and enforces the forbidden API list (System.IO, Process, Threading, Reflection, raw network).
///
/// For now, this implementation performs basic syntax validation.
///
/// <para>
/// <b>SECURITY LIMITATION (TemplateEngine-006):</b> the forbidden-API check below
/// is an interim, <i>advisory</i> text scan — it is NOT an authoritative trust-model
/// boundary. <see cref="CSharpDelimiterScanner.ContainsInCode"/> removes the
/// false-positive half (forbidden text inside a string/comment is ignored), but a
/// determined script can still bypass the literal patterns via namespace aliases,
/// <c>using static</c>, or <c>global::</c>-qualified references. Authoritative
/// enforcement requires Roslyn semantic symbol analysis of the referenced
/// types/namespaces and is the responsibility of the real script compiler and the
/// Site Runtime sandbox. Do not rely on this class as the sole trust-model gate.
/// </para>
/// </summary>
public class ScriptCompiler
{
    /// <summary>
    /// Forbidden namespace patterns — scripts (and trigger expressions, via
    /// <see cref="ValidationService"/>) must not use these. Trigger expressions run
    /// under the same trust model as scripts, so the list is shared from here rather
    /// than duplicated.
    ///
    /// <para>
    /// Matched with <see cref="CSharpDelimiterScanner.ContainsInCode"/> against code
    /// regions only. This is advisory — see the class summary's SECURITY LIMITATION
    /// note; the substring patterns are bypassable and the authoritative check is
    /// deferred to Roslyn semantic analysis.
    /// </para>
    /// </summary>
    internal static readonly string[] ForbiddenPatterns =
    [
        "System.IO.",
        "System.Diagnostics.Process",
        "System.Threading.",
        "System.Reflection.",
        "System.Net.Sockets.",
        "System.Net.Http.",
    ];

    /// <summary>
    /// Attempts to compile a script and returns success or a compilation error.
    /// </summary>
    /// <param name="code">The C# script code.</param>
    /// <param name="scriptName">The canonical name of the script (for error messages).</param>
    /// <returns>Success if the script compiles, or Failure with the error message.</returns>
    public Result<bool> TryCompile(string code, string scriptName)
    {
        if (string.IsNullOrWhiteSpace(code))
            return Result<bool>.Failure($"Script '{scriptName}' has empty code.");

        // Check for forbidden APIs. Advisory only (see class summary): the scan is
        // code-region-aware so forbidden text inside a string/comment is ignored,
        // but it remains a substring match and is not an authoritative boundary.
        foreach (var pattern in ForbiddenPatterns)
        {
            if (CSharpDelimiterScanner.ContainsInCode(code, pattern))
            {
                return Result<bool>.Failure(
                    $"Script '{scriptName}' uses forbidden API: '{pattern.TrimEnd('.')}'. " +
                    "Scripts cannot use System.IO, Process, Threading, Reflection, or raw network APIs.");
            }
        }

        // Basic structural validation: balanced braces/brackets/parens. The scan
        // is string- and comment-aware (see CSharpDelimiterScanner) so a delimiter
        // inside a regular/verbatim/interpolated/raw string, a char literal, or a
        // comment does not produce a false mismatch. This remains an interim check
        // until the Roslyn-based compiler is wired in.
        var mismatch = CSharpDelimiterScanner.Scan(code);
        return mismatch switch
        {
            CSharpDelimiterScanner.Mismatch.None =>
                Result<bool>.Success(true),
            CSharpDelimiterScanner.Mismatch.UnexpectedCloseBrace =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched braces (unexpected closing brace)."),
            CSharpDelimiterScanner.Mismatch.UnclosedBrace =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched braces (unclosed opening brace)."),
            CSharpDelimiterScanner.Mismatch.UnexpectedCloseBracket =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched brackets (unexpected closing bracket)."),
            CSharpDelimiterScanner.Mismatch.UnclosedBracket =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched brackets (unclosed opening bracket)."),
            CSharpDelimiterScanner.Mismatch.UnexpectedCloseParen =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched parentheses (unexpected closing parenthesis)."),
            CSharpDelimiterScanner.Mismatch.UnclosedParen =>
                Result<bool>.Failure($"Script '{scriptName}' has mismatched parentheses (unclosed opening parenthesis)."),
            CSharpDelimiterScanner.Mismatch.UnclosedBlockComment =>
                Result<bool>.Failure($"Script '{scriptName}' has an unclosed block comment."),
            CSharpDelimiterScanner.Mismatch.UnterminatedString =>
                Result<bool>.Failure($"Script '{scriptName}' has an unterminated string literal."),
            CSharpDelimiterScanner.Mismatch.UnterminatedChar =>
                Result<bool>.Failure($"Script '{scriptName}' has an unterminated character literal."),
            _ => Result<bool>.Success(true),
        };
    }
}
