using System.Globalization; using System.Text; namespace line_gestao_api.Services; internal static class MveAuditNormalization { public static string NormalizeHeader(string? value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } var normalized = value.Trim().ToUpperInvariant().Normalize(NormalizationForm.FormD); var builder = new StringBuilder(normalized.Length); foreach (var ch in normalized) { if (CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark) { builder.Append(ch); } } return builder.ToString() .Replace("\u00A0", " ") .Trim(); } public static string CleanTextValue(string? value, bool removeSingleQuotes = true) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } var cleaned = value .Replace("\u00A0", " ") .Replace("\t", " ") .Replace("\r", " ") .Replace("\n", " ") .Trim(); if (removeSingleQuotes) { cleaned = cleaned.Replace("'", string.Empty); } while (cleaned.Contains(" ", StringComparison.Ordinal)) { cleaned = cleaned.Replace(" ", " ", StringComparison.Ordinal); } return cleaned.Trim(); } public static string OnlyDigits(string? value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } var builder = new StringBuilder(value.Length); foreach (var ch in value) { if (char.IsDigit(ch)) { builder.Append(ch); } } return builder.ToString(); } public static string? NullIfEmptyDigits(string? value) { var digits = OnlyDigits(value); return string.IsNullOrWhiteSpace(digits) ? null : digits; } public static string NormalizePhoneDigits(string? value) { var digits = OnlyDigits(value); if (string.IsNullOrWhiteSpace(digits)) { return string.Empty; } if (digits.StartsWith("55", StringComparison.Ordinal) && digits.Length is 12 or 13) { digits = digits[2..]; } return digits.Length > 11 ? digits[^11..] : digits; } public static string ExtractPhoneDdd(string? value) { var digits = NormalizePhoneDigits(value); return digits.Length >= 10 ? digits[..2] : string.Empty; } public static string ExtractPhoneLocalNumber(string? value) { var digits = NormalizePhoneDigits(value); return digits.Length >= 10 ? digits[2..] : digits; } public static string NormalizeComparableText(string? value) { var cleaned = CleanTextValue(value); if (string.IsNullOrWhiteSpace(cleaned)) { return string.Empty; } return NormalizeHeader(cleaned) .Replace(" ", " ", StringComparison.Ordinal) .Trim(); } public static string NormalizeAccountLike(string? value) { var digits = OnlyDigits(value); if (!string.IsNullOrWhiteSpace(digits)) { return digits; } return NormalizeComparableText(value); } public static DateTime? ParseDateValue(string? rawValue) { var cleaned = CleanTextValue(rawValue); if (string.IsNullOrWhiteSpace(cleaned)) { return null; } if (double.TryParse( cleaned.Replace(",", ".", StringComparison.Ordinal), NumberStyles.Float, CultureInfo.InvariantCulture, out var oaValue) && oaValue > 10_000 && oaValue < 90_000) { try { return ToUtcDateOnly(DateTime.FromOADate(oaValue)); } catch { // segue para os demais formatos } } var formats = new[] { "dd/MM/yyyy", "d/M/yyyy", "dd/MM/yy", "d/M/yy", "yyyy-MM-dd", "dd-MM-yyyy", "d-M-yyyy", "yyyyMMdd" }; foreach (var format in formats) { if (DateTime.TryParseExact( cleaned, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact)) { return ToUtcDateOnly(exact); } } if (DateTime.TryParse(cleaned, new CultureInfo("pt-BR"), DateTimeStyles.None, out var parsedBr)) { return ToUtcDateOnly(parsedBr); } if (DateTime.TryParse(cleaned, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedInvariant)) { return ToUtcDateOnly(parsedInvariant); } return null; } public static DateTime ToUtcDateOnly(DateTime date) { return new DateTime(date.Year, date.Month, date.Day, 12, 0, 0, DateTimeKind.Utc); } public static string FormatDate(DateTime? value) { return value.HasValue ? value.Value.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture) : string.Empty; } public static MveNormalizedStatus NormalizeReportStatus(string? rawValue) { var displayValue = CleanTextValue(rawValue); if (string.IsNullOrWhiteSpace(displayValue)) { return new MveNormalizedStatus(string.Empty, string.Empty, false); } var headSegment = NormalizeComparableText(displayValue.Split(':', 2)[0]) .Replace("/", " ", StringComparison.Ordinal) .Replace("-", " ", StringComparison.Ordinal) .Replace(".", " ", StringComparison.Ordinal) .Replace("(", " ", StringComparison.Ordinal) .Replace(")", " ", StringComparison.Ordinal) .Replace(" ", " ", StringComparison.Ordinal) .Trim(); var canonical = NormalizeComparableText(displayValue) .Replace("/", " ", StringComparison.Ordinal) .Replace("-", " ", StringComparison.Ordinal) .Replace(".", " ", StringComparison.Ordinal) .Replace("(", " ", StringComparison.Ordinal) .Replace(")", " ", StringComparison.Ordinal) .Replace(" ", " ", StringComparison.Ordinal) .Trim(); var key = headSegment switch { var text when text.Contains("ATIVO", StringComparison.Ordinal) || text == "ATIVA" => "ATIVO", var text when text.Contains("BLOQUEIO PARCIAL", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("PRE ATIVACAO", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("CANCEL", StringComparison.Ordinal) => "BLOQUEIO_120_DIAS", var text when text.Contains("SUSPENS", StringComparison.Ordinal) => "SUSPENSO", var text when text.Contains("PENDENTE", StringComparison.Ordinal) && text.Contains("TROCA", StringComparison.Ordinal) && text.Contains("NUMERO", StringComparison.Ordinal) => "PENDENTE_TROCA_NUMERO", var text when text.Contains("PERDA", StringComparison.Ordinal) || text.Contains("ROUBO", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("BLOQUEIO", StringComparison.Ordinal) && text.Contains("120", StringComparison.Ordinal) => "BLOQUEIO_120_DIAS", _ => canonical.Replace(" ", "_", StringComparison.Ordinal) }; var recognized = key is "ATIVO" or "BLOQUEIO_PERDA_ROUBO" or "BLOQUEIO_120_DIAS" or "SUSPENSO" or "PENDENTE_TROCA_NUMERO"; return new MveNormalizedStatus(displayValue, key, recognized); } public static MveNormalizedStatus NormalizeSystemStatus(string? rawValue) { var displayValue = CleanTextValue(rawValue); if (string.IsNullOrWhiteSpace(displayValue)) { return new MveNormalizedStatus(string.Empty, string.Empty, false); } var canonical = NormalizeComparableText(displayValue) .Replace("/", " ", StringComparison.Ordinal) .Replace("-", " ", StringComparison.Ordinal) .Replace(".", " ", StringComparison.Ordinal) .Replace(":", " ", StringComparison.Ordinal) .Replace("(", " ", StringComparison.Ordinal) .Replace(")", " ", StringComparison.Ordinal) .Replace(" ", " ", StringComparison.Ordinal) .Trim(); var key = canonical switch { var text when text.Contains("ATIVO", StringComparison.Ordinal) || text == "ATIVA" => "ATIVO", var text when text.Contains("PERDA", StringComparison.Ordinal) || text.Contains("ROUBO", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("BLOQUEIO PARCIAL", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("PRE ATIVACAO", StringComparison.Ordinal) => "BLOQUEIO_PERDA_ROUBO", var text when text.Contains("BLOQUEIO", StringComparison.Ordinal) && text.Contains("120", StringComparison.Ordinal) => "BLOQUEIO_120_DIAS", var text when text.Contains("CANCEL", StringComparison.Ordinal) => "CANCELADO", var text when text.Contains("SUSPENS", StringComparison.Ordinal) => "SUSPENSO", var text when text.Contains("PENDENTE", StringComparison.Ordinal) && text.Contains("TROCA", StringComparison.Ordinal) && text.Contains("NUMERO", StringComparison.Ordinal) => "PENDENTE_TROCA_NUMERO", _ => canonical.Replace(" ", "_", StringComparison.Ordinal) }; var recognized = key is "ATIVO" or "BLOQUEIO_PERDA_ROUBO" or "BLOQUEIO_120_DIAS" or "CANCELADO" or "SUSPENSO" or "PENDENTE_TROCA_NUMERO"; return new MveNormalizedStatus(displayValue, key, recognized); } public static MveNormalizedStatus NormalizeStatus(string? rawValue) { return NormalizeSystemStatus(rawValue); } public static string NormalizeStatusForSystem(string? rawValue) { var normalized = NormalizeReportStatus(rawValue); return normalized.Key switch { "ATIVO" => "ATIVO", "BLOQUEIO_PERDA_ROUBO" => "BLOQUEIO PERDA/ROUBO", "BLOQUEIO_120_DIAS" => "BLOQUEIO 120 DIAS", "SUSPENSO" => "SUSPENSO", "PENDENTE_TROCA_NUMERO" => "PENDENTE TROCA NUMERO", _ => normalized.DisplayValue }; } } internal sealed record MveNormalizedStatus(string DisplayValue, string Key, bool Recognized);