using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using GestionaDenuncias.Shared.Models;

namespace ApiDenuncias.Helpers;

public static class ReportParser
{
    private static readonly HashSet<string> FlatReportSections = NormalizeAll(
        "Datos del denunciante",
        "Descripción",
        "Preferencias de notificación",
        "Condiciones y reglas de uso",
        "Comments");

    private static readonly HashSet<string> FlatReportLabels = NormalizeAll(
        "Indique si actúa como persona física o en representación de una persona jurídica.",
        "Nombre",
        "1º Apellido",
        "2º Apellido",
        "SEXO",
        "CONTACTO TELEFÓNICO",
        "País de Origen",
        "NIF (DNI, NIE)",
        "Razón social",
        "CIF",
        "Otro documento identificativo",
        "Asunto",
        "¿A quién denuncia?",
        "Especifique a quién denuncia",
        "Describa su denuncia",
        "¿Ha denunciado estos hechos ante otras instituciones u órganos?",
        "POR FAVOR. INDIQUE EL ORGANISMO O LA INSTITUCION DONDE HA DENUNCIADO LOS HECHOS",
        "¿Solicita medidas concretas de protección?",
        "DESCRIBA LAS MEDIDAS DE PROTECCIÓN SOLICITADAS",
        "Lugar en el que ocurrieron los hechos que denuncia",
        "Fecha de los hechos que denuncia",
        "Autorización para remitir su denuncia",
        "En tal caso, ¿desea que su denuncia se remita anonimizada (sin sus datos personales)?",
        "En tal caso, ¿desea que su denuncia se remita anonimizada (sin datos personales)?",
        "Preferencia de notificación",
        "Notificaciones Electrónicas",
        "Correo electrónico",
        "Seguimiento Online",
        "Autorizo recibir notificaciones vía Correo Postal",
        "Provincia",
        "Tipo de vía",
        "Nombre de la vía",
        "Código Postal",
        "Localidad",
        "Municipio",
        "Número/Km",
        "Número",
        "Bloque",
        "Escalera",
        "Planta",
        "Piso",
        "Puerta",
        "Extra",
        "Condiciones y reglas de uso del buzón de denuncias",
        "TRATAMIENTO DE DATOS PERSONALES");

    public static DenunciasGestiona ParseReport(string reportText)
    {
        var lines = NormalizeLines(reportText);
        var denuncia = new DenunciasGestiona
        {
            TextoOriginalReport = reportText ?? string.Empty
        };

        ParseMetadata(lines, denuncia);

        var fields = ParseFormFields(lines, out var comments);
        denuncia.SetCamposFormulario(fields);

        if (!string.IsNullOrWhiteSpace(comments))
        {
            denuncia.Comments = comments;
        }

        ApplyFieldMappings(denuncia, fields);
        FinalizeReporterData(denuncia);
        return denuncia;
    }

    private static string[] NormalizeLines(string? reportText)
    {
        return (reportText ?? string.Empty)
            .Replace("\r\n", "\n")
            .Replace('\r', '\n')
            .Split('\n');
    }

    private static void ParseMetadata(string[] lines, DenunciasGestiona denuncia)
    {
        foreach (var rawLine in lines)
        {
            var line = rawLine.Trim();
            if (string.IsNullOrWhiteSpace(line))
            {
                continue;
            }

            if (line.StartsWith("ID:", StringComparison.OrdinalIgnoreCase))
            {
                if (int.TryParse(line[3..].Trim(), out var id))
                {
                    denuncia.Id_Denuncia = id;
                }

                continue;
            }

            if (line.StartsWith("Fecha:", StringComparison.OrdinalIgnoreCase))
            {
                var value = line[6..].Trim();
                if (value.EndsWith("(UTC)", StringComparison.OrdinalIgnoreCase))
                {
                    value = value[..^5].Trim();
                }

                if (DateTime.TryParseExact(
                        value,
                        "dddd dd MMMM yyyy HH:mm",
                        CultureInfo.InvariantCulture,
                        DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                        out var parsedDate))
                {
                    denuncia.Fecha = parsedDate;
                }
                else if (DateTime.TryParse(
                             value,
                             CultureInfo.InvariantCulture,
                             DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                             out parsedDate))
                {
                    denuncia.Fecha = parsedDate;
                }

                continue;
            }

            if (line.StartsWith("Etiqueta:", StringComparison.OrdinalIgnoreCase))
            {
                denuncia.Etiqueta = line[9..].Trim();
                continue;
            }

            if (line.StartsWith("Estado:", StringComparison.OrdinalIgnoreCase))
            {
                denuncia.Estado = line[7..].Trim();
            }
        }
    }

    private static List<ReportFieldEntry> ParseFormFields(string[] lines, out string comments)
    {
        var fields = new List<ReportFieldEntry>();
        var commentBuilder = new StringBuilder();
        var currentSection = string.Empty;
        var order = 0;
        var messagesSection = false;

        for (var i = 0; i < lines.Length; i++)
        {
            var rawLine = lines[i];
            var trimmed = rawLine.Trim();

            if (messagesSection)
            {
                if (!string.IsNullOrWhiteSpace(trimmed))
                {
                    if (commentBuilder.Length > 0)
                    {
                        commentBuilder.AppendLine();
                    }

                    commentBuilder.Append(trimmed);
                }

                continue;
            }

            if (string.IsNullOrWhiteSpace(trimmed) || IsMetadataLine(trimmed))
            {
                continue;
            }

            if (string.Equals(trimmed, "{Messages}", StringComparison.OrdinalIgnoreCase))
            {
                messagesSection = true;
                continue;
            }

            var indent = CountIndentation(rawLine);
            if (indent == 0)
            {
                currentSection = trimmed;
                continue;
            }

            var label = trimmed;
            var valueLines = new List<string>();
            var j = i + 1;

            while (j < lines.Length)
            {
                var nextRaw = lines[j];
                var nextTrimmed = nextRaw.Trim();

                if (string.Equals(nextTrimmed, "{Messages}", StringComparison.OrdinalIgnoreCase))
                {
                    break;
                }

                if (string.IsNullOrWhiteSpace(nextTrimmed))
                {
                    j++;
                    continue;
                }

                var nextIndent = CountIndentation(nextRaw);
                if (nextIndent <= indent)
                {
                    break;
                }

                valueLines.Add(nextTrimmed);
                j++;
            }

            fields.Add(new ReportFieldEntry
            {
                Order = ++order,
                Section = currentSection,
                Label = label,
                Value = string.Join(Environment.NewLine, valueLines)
            });

            i = j - 1;
        }

        comments = commentBuilder.ToString().Trim();
        if (fields.Count == 0)
        {
            fields = ParseFlatFormFields(lines, out comments);
        }

        return fields;
    }

    private static List<ReportFieldEntry> ParseFlatFormFields(string[] lines, out string comments)
    {
        var fields = new List<ReportFieldEntry>();
        var commentBuilder = new StringBuilder();
        var currentSection = string.Empty;
        var order = 0;

        for (var i = 0; i < lines.Length; i++)
        {
            var trimmed = lines[i].Trim();
            if (ShouldSkipFlatLine(trimmed) || IsMetadataLine(trimmed))
            {
                continue;
            }

            if (IsMessagesStart(trimmed))
            {
                for (var j = i + 1; j < lines.Length; j++)
                {
                    var commentLine = lines[j].Trim();
                    if (ShouldSkipFlatLine(commentLine))
                    {
                        continue;
                    }

                    if (commentBuilder.Length > 0)
                    {
                        commentBuilder.AppendLine();
                    }

                    commentBuilder.Append(commentLine);
                }

                break;
            }

            if (IsFlatSection(trimmed))
            {
                currentSection = trimmed;
                continue;
            }

            if (!IsFlatLabel(trimmed))
            {
                continue;
            }

            var valueLines = new List<string>();
            var nextIndex = i + 1;
            while (nextIndex < lines.Length)
            {
                var nextTrimmed = lines[nextIndex].Trim();
                if (ShouldSkipFlatLine(nextTrimmed))
                {
                    nextIndex++;
                    continue;
                }

                if (IsMetadataLine(nextTrimmed) ||
                    IsMessagesStart(nextTrimmed) ||
                    IsFlatSection(nextTrimmed) ||
                    IsFlatLabel(nextTrimmed))
                {
                    break;
                }

                valueLines.Add(nextTrimmed);
                nextIndex++;
            }

            fields.Add(new ReportFieldEntry
            {
                Order = ++order,
                Section = currentSection,
                Label = trimmed,
                Value = string.Join(Environment.NewLine, valueLines)
            });

            i = nextIndex - 1;
        }

        comments = commentBuilder.ToString().Trim();
        return fields;
    }

    private static void ApplyFieldMappings(DenunciasGestiona denuncia, IReadOnlyList<ReportFieldEntry> fields)
    {
        var lookup = fields
            .GroupBy(field => Normalize(field.Label))
            .ToDictionary(
                group => group.Key,
                group => group
                    .Select(entry => entry.Value.Trim())
                    .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty,
                StringComparer.OrdinalIgnoreCase);

        denuncia.TipoDenunciante = FindValue(
            lookup,
            "indique si actua como persona fisica o en representacion de una persona juridica");
        denuncia.EsPersonaJuridica = denuncia.TipoDenunciante.Contains("jurid", StringComparison.OrdinalIgnoreCase);

        denuncia.Nombre = FindValue(lookup, "nombre");
        denuncia.PrimerApellido = FindValue(lookup, "1 apellido", "primer apellido");
        denuncia.SegundoApellido = FindValue(lookup, "2 apellido", "segundo apellido");
        denuncia.Apellidos = string.Join(
            ' ',
            new[] { denuncia.PrimerApellido, denuncia.SegundoApellido }
                .Where(value => !string.IsNullOrWhiteSpace(value)));
        denuncia.RazonSocial = FindValue(lookup, "razon social");
        denuncia.Sexo = NormalizeGender(FindValue(lookup, "sexo"));
        denuncia.Telefono = FindValue(lookup, "contacto telefonico", "telefono", "telefono movil");
        denuncia.PaisOrigen = FindValue(lookup, "pais de origen");

        if (TryGetValue(lookup, out var identifier, "nif dni nie"))
        {
            denuncia.Dni = identifier;
            denuncia.TipoDocumentoIdentificativo = "NIF (DNI, NIE)";
        }
        else if (TryGetValue(lookup, out identifier, "cif"))
        {
            denuncia.Dni = identifier;
            denuncia.TipoDocumentoIdentificativo = "CIF";
        }
        else if (TryGetValue(lookup, out identifier, "otro documento identificativo"))
        {
            denuncia.Dni = identifier;
            denuncia.TipoDocumentoIdentificativo = "Otro documento identificativo";
        }

        denuncia.Asunto = FindValue(lookup, "asunto");
        denuncia.A_Quien_Denuncia = FindValue(lookup, "a quien denuncia");
        denuncia.DenunciadoDetalle = FindValue(lookup, "especifique a quien denuncia");
        denuncia.Descripcion_Denuncia = FindValue(lookup, "describa su denuncia");
        denuncia.Denunciado_Ante_Inst = FindValue(
            lookup,
            "ha denunciado estos hechos ante otras instituciones u organos");
        denuncia.OrganismoDenunciado = FindValue(
            lookup,
            "por favor indique el organismo o la institucion donde ha denunciado los hechos",
            "por favor indique el organismo o la institucion donde ha denunciado los hechos");
        denuncia.SolicitaProteccion = FindValue(
            lookup,
            "solicita medidas concretas de proteccion");
        denuncia.MedidasProteccionSolicitadas = FindValue(
            lookup,
            "describa las medidas de proteccion solicitadas");
        denuncia.Lugar_Hechos = FindValue(
            lookup,
            "lugar en el que ocurrieron los hechos que denuncia",
            "lugar en la que ocurrieron los hechos que denuncia");

        var fechaHechos = FindValue(lookup, "fecha de los hechos que denuncia");
        if (TryParseSpanishDate(fechaHechos, out var parsedFactsDate))
        {
            denuncia.Fecha_Hechos = parsedFactsDate;
        }

        denuncia.AutorizaRemision = FindValue(lookup, "autorizacion para remitir su denuncia");
        denuncia.PreferenciaRemision = FindValue(
            lookup,
            "en tal caso desea que su denuncia se remita anonimizada sin datos personales");

        denuncia.Notificacion_Preferencia = FindValue(lookup, "preferencia de notificacion");
        denuncia.Notificacion_Electronica = FindValue(lookup, "notificaciones electronicas");
        denuncia.SeguimientoOnline = FindValue(lookup, "seguimiento online");
        denuncia.NotificacionPostal = FindValue(lookup, "autorizo recibir notificaciones via correo postal");
        denuncia.Correo_Electronico = FindValue(lookup, "correo electronico");
        denuncia.Provincia = FindValue(lookup, "provincia");
        denuncia.DireccionTipoVia = NormalizeRoadType(FindValue(lookup, "tipo de via"));
        denuncia.Direccion = FindValue(lookup, "nombre de la via", "direccion", "domicilio");
        denuncia.CodigoPostal = FindValue(lookup, "codigo postal", "c p");
        denuncia.Municipio = FindValue(lookup, "localidad", "municipio", "poblacion");
        denuncia.DireccionNumero = FindValue(lookup, "numero km", "numero");
        denuncia.DireccionBloque = FindValue(lookup, "bloque");
        denuncia.DireccionEscalera = FindValue(lookup, "escalera");
        denuncia.DireccionPiso = FindValue(lookup, "planta", "piso");
        denuncia.DireccionPuerta = FindValue(lookup, "puerta");
        denuncia.DireccionExtra = FindValue(lookup, "extra");

        denuncia.Condiciones = fields.Any(field =>
            field.Section.Contains("Condiciones", StringComparison.OrdinalIgnoreCase) &&
            (field.Value.Contains("☑", StringComparison.Ordinal) ||
             field.Value.Contains("Acepto", StringComparison.OrdinalIgnoreCase)));

        if (string.IsNullOrWhiteSpace(denuncia.Pais))
        {
            denuncia.Pais = denuncia.PaisOrigen;
        }
    }

    private static void FinalizeReporterData(DenunciasGestiona denuncia)
    {
        if (string.IsNullOrWhiteSpace(denuncia.TipoDenunciante))
        {
            denuncia.TipoDenunciante = string.IsNullOrWhiteSpace(denuncia.RazonSocial) &&
                                       string.IsNullOrWhiteSpace(denuncia.Nombre)
                ? "Anonima"
                : "Persona fisica";
        }

        if (denuncia.EsPersonaJuridica && string.IsNullOrWhiteSpace(denuncia.RazonSocial))
        {
            denuncia.RazonSocial = denuncia.NombreDenuncianteMostrar;
        }

        if (string.IsNullOrWhiteSpace(denuncia.Tipo_Denuncia))
        {
            denuncia.Tipo_Denuncia = string.IsNullOrWhiteSpace(denuncia.RazonSocial) &&
                                     string.IsNullOrWhiteSpace(denuncia.Nombre)
                ? "Anonima"
                : "No anonima, deseo identificarme";
        }

        denuncia.Confidencial = DenunciasGestiona.IndicaDenunciaAnonima(denuncia.Tipo_Denuncia) ||
                                !DenunciasGestiona.IndicaDenunciaIdentificada(denuncia.Tipo_Denuncia) &&
                                string.IsNullOrWhiteSpace(denuncia.RazonSocialResuelta) &&
                                string.IsNullOrWhiteSpace(denuncia.NombreResuelto) &&
                                string.IsNullOrWhiteSpace(denuncia.DocumentoResuelto);
    }

    private static string FindValue(
        IReadOnlyDictionary<string, string> lookup,
        params string[] candidateLabels)
    {
        return TryGetValue(lookup, out var value, candidateLabels) ? value : string.Empty;
    }

    private static bool TryGetValue(
        IReadOnlyDictionary<string, string> lookup,
        out string value,
        params string[] candidateLabels)
    {
        foreach (var candidateLabel in candidateLabels)
        {
            if (lookup.TryGetValue(Normalize(candidateLabel), out value!) &&
                !string.IsNullOrWhiteSpace(value))
            {
                return true;
            }
        }

        value = string.Empty;
        return false;
    }

    private static bool TryParseSpanishDate(string? value, out DateTime result)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            result = DateTime.MinValue;
            return false;
        }

        if (DateTime.TryParseExact(
                value,
                new[] { "dd/MM/yyyy", "d/M/yyyy", "dd-MM-yyyy", "d-M-yyyy" },
                CultureInfo.GetCultureInfo("es-ES"),
                DateTimeStyles.None,
                out result))
        {
            return true;
        }

        return DateTime.TryParse(value, CultureInfo.GetCultureInfo("es-ES"), DateTimeStyles.None, out result);
    }

    private static string NormalizeGender(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        return Normalize(value) switch
        {
            "masculino" or "hombre" => "Hombre",
            "femenino" or "mujer" => "Mujer",
            _ => value.Trim()
        };
    }

    private static string NormalizeRoadType(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var normalized = Normalize(value);
        return normalized switch
        {
            "calle" => "CL",
            "avenida" => "AV",
            "plaza" => "PZ",
            "carretera" => "CR",
            "camino" => "CM",
            "poligono" => "PG",
            _ => value.Trim().ToUpperInvariant()
        };
    }

    private static bool IsMetadataLine(string line)
    {
        return line.StartsWith("ID:", StringComparison.OrdinalIgnoreCase) ||
               line.StartsWith("Fecha:", StringComparison.OrdinalIgnoreCase) ||
               line.StartsWith("Etiqueta:", StringComparison.OrdinalIgnoreCase) ||
               line.StartsWith("Estado:", StringComparison.OrdinalIgnoreCase);
    }

    private static HashSet<string> NormalizeAll(params string[] values)
        => values
            .Select(Normalize)
            .Where(value => !string.IsNullOrWhiteSpace(value))
            .ToHashSet(StringComparer.Ordinal);

    private static bool IsFlatSection(string value)
        => FlatReportSections.Contains(Normalize(value));

    private static bool IsFlatLabel(string value)
        => FlatReportLabels.Contains(Normalize(value));

    private static bool IsMessagesStart(string value)
    {
        var normalized = Normalize(value);
        return normalized == "messages" ||
               normalized == "comments" ||
               normalized.StartsWith("comments ", StringComparison.Ordinal);
    }

    private static bool ShouldSkipFlatLine(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return true;
        }

        return Regex.IsMatch(value, @"^\d+/\d+$", RegexOptions.CultureInvariant) ||
               Regex.IsMatch(value, @"^REPORT\s+\d+\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
    }

    private static int CountIndentation(string value)
    {
        var indent = 0;
        foreach (var character in value)
        {
            if (character == ' ')
            {
                indent++;
                continue;
            }

            if (character == '\t')
            {
                indent += 4;
                continue;
            }

            break;
        }

        return indent;
    }

    private static string Normalize(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var normalized = value.Normalize(NormalizationForm.FormD);
        var builder = new StringBuilder(normalized.Length);

        foreach (var character in normalized)
        {
            var category = CharUnicodeInfo.GetUnicodeCategory(character);
            if (category == UnicodeCategory.NonSpacingMark)
            {
                continue;
            }

            if (character is 'º' or 'ª')
            {
                continue;
            }

            builder.Append(char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : ' ');
        }

        return Regex.Replace(builder.ToString(), @"\s+", " ").Trim();
    }
}
