using System.Text.RegularExpressions;

namespace Nalu.Web.PostProcessors;

/// Validates CNPJ check digits (mod 11 algorithm) for both numeric and alphanumeric CNPJs
/// (IN RFB 2229/2024). Digits use face value (0–9); letters use raw ASCII (A=65…Z=90).
/// Last two characters must always be numeric check digits.
/// Formats as XX.XXX.XXX/XXXX-XX.
public class ValidateCnpjDigit : IPostProcessor
{
    public string Name => "validate_cnpj_digit";

    public ProcessorResult Process(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return ProcessorResult.Invalid("CNPJ não informado");

        // Uppercase and strip separators only (keep alphanumeric)
        var normalized = Regex.Replace(value.ToUpperInvariant(), @"[.\-/\s]", "");

        if (normalized.Length != 14)
            return ProcessorResult.Invalid($"CNPJ deve ter 14 caracteres (encontrado: {normalized.Length})");

        // Validate character set: only digits and uppercase A-Z
        if (!normalized.All(c => char.IsDigit(c) || (c >= 'A' && c <= 'Z')))
            return ProcessorResult.Invalid("CNPJ contém caracteres inválidos");

        // Last two characters must be digits (check digits are always numeric)
        if (!normalized[12..].All(char.IsDigit))
            return ProcessorResult.Invalid("Os dois últimos caracteres do CNPJ devem ser dígitos");

        // Reject all-same-character sequences (000…0, AAA…A, etc.)
        if (normalized.Distinct().Count() == 1)
            return ProcessorResult.Invalid("CNPJ inválido (sequência de caracteres repetidos)");

        if (!CheckDigits(normalized))
            return ProcessorResult.Invalid("CNPJ inválido (dígitos verificadores incorretos)");

        // Format XX.XXX.XXX/XXXX-XX
        var formatted = $"{normalized[..2]}.{normalized[2..5]}.{normalized[5..8]}/{normalized[8..12]}-{normalized[12..]}";
        return ProcessorResult.Ok(formatted);
    }

    /// Maps a character to its numeric value for the mod 11 calculation.
    /// Digits: face value (0–9). Letters: raw ASCII value (A=65 … Z=90).
    /// Per Receita Federal CNPJ alphanumeric spec (COCAD).
    private static int CharValue(char c) => char.IsDigit(c) ? c - '0' : (int)c;

    private static bool CheckDigits(string s)
    {
        // First check digit (position 12, 0-indexed)
        int[] w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
        var sum1 = w1.Select((w, i) => CharValue(s[i]) * w).Sum();
        var r1 = sum1 % 11;
        var cd1 = r1 < 2 ? 0 : 11 - r1;
        if (s[12] - '0' != cd1) return false;

        // Second check digit (position 13, 0-indexed)
        int[] w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
        var sum2 = w2.Select((w, i) => CharValue(s[i]) * w).Sum();
        var r2 = sum2 % 11;
        var cd2 = r2 < 2 ? 0 : 11 - r2;
        return s[13] - '0' == cd2;
    }
}
