using System.Text.RegularExpressions;

namespace Nalu.Web.Services;

/// <summary>
/// Detects suspicious prompt-injection patterns in user inputs and enforces length limits.
/// Does NOT block — logs a warning so the team has visibility.
/// </summary>
public static class InputGuard
{
    public const int MaxUserInput    = 1000;
    public const int MaxAgentInput   = 600;
    public const int MaxAgentContext = 2000;
    public const int MaxSuggestion   = 800;

    private static readonly Regex SuspiciousPattern = new(
        @"ignore\s+(all\s+)?(previous|above|prior|earlier)\s+instructions?" +
        @"|forget\s+(all\s+)?(previous|above|prior|earlier)\s+instructions?" +
        @"|system\s*:" +
        @"|<\s*system\s*>" +
        @"|\bprompt\s+injection\b" +
        @"|you\s+are\s+now\s+(in\s+)?(developer|god|admin|sudo)\s+mode" +
        @"|new\s+instructions?" +
        @"|override\s+(your\s+)?(previous\s+)?instructions?",
        RegexOptions.IgnoreCase | RegexOptions.Compiled,
        TimeSpan.FromMilliseconds(50));

    /// <summary>Returns true and logs if the input contains injection patterns.</summary>
    public static bool IsSuspicious(string? input, string fieldName, ILogger logger, string validatorId)
    {
        if (string.IsNullOrWhiteSpace(input)) return false;

        try
        {
            if (SuspiciousPattern.IsMatch(input))
            {
                logger.LogWarning(
                    "Possible prompt injection in {Field} for validator {Validator}. Input (truncated): {Input}",
                    fieldName, validatorId, input[..Math.Min(input.Length, 200)]);
                return true;
            }
        }
        catch (RegexMatchTimeoutException) { /* skip on timeout */ }

        return false;
    }

    /// <summary>Truncates suggestion output to MaxSuggestion chars.</summary>
    public static string? ClampSuggestion(string? suggestion) =>
        suggestion is null ? null
        : suggestion.Length <= MaxSuggestion ? suggestion
        : suggestion[..MaxSuggestion].TrimEnd() + "…";

    // Patterns that must never appear in LLM-generated suggestions.
    private static readonly Regex SuggestionDangerPattern = new(
        @"R\$\s*\d"      +  // monetary amounts
        @"|https?://"    +  // URLs
        @"|System\s*:",      // system-prefix (potential role escalation)
        RegexOptions.IgnoreCase | RegexOptions.Compiled,
        TimeSpan.FromMilliseconds(50));

    /// <summary>
    /// Validates LLM-generated suggestion output.
    /// Returns null (and logs) if suggestion contains dangerous patterns
    /// or verbatim-echoes the user input. Also clamps length.
    /// </summary>
    public static string? ValidateSuggestion(
        string? suggestion,
        string? userInput,
        ILogger logger,
        string validatorId)
    {
        if (suggestion is null) return null;

        suggestion = ClampSuggestion(suggestion) ?? suggestion;

        try
        {
            if (SuggestionDangerPattern.IsMatch(suggestion))
            {
                logger.LogWarning(
                    "Suggestion rejected (danger pattern) for {Validator}: {Suggestion}",
                    validatorId, suggestion[..Math.Min(suggestion.Length, 200)]);
                return null;
            }

            // Reject if suggestion verbatim echoes a substantial chunk of user input
            // (indicates the LLM was manipulated into reflecting injected content).
            if (!string.IsNullOrWhiteSpace(userInput) && userInput.Length >= 20 &&
                suggestion.Contains(userInput.Trim(), StringComparison.OrdinalIgnoreCase))
            {
                logger.LogWarning(
                    "Suggestion rejected (echoes user input) for {Validator}.", validatorId);
                return null;
            }
        }
        catch (RegexMatchTimeoutException) { /* pass through on timeout */ }

        return suggestion;
    }

    /// <summary>
    /// Escapes XML special characters so user content cannot break out of XML delimiter tags.
    /// </summary>
    public static string XmlEscape(string? value) =>
        (value ?? "")
        .Replace("&", "&amp;")
        .Replace("<", "&lt;")
        .Replace(">", "&gt;")
        .Replace("\"", "&quot;")
        .Replace("'", "&apos;");
}
