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

namespace ApiDenuncias.Helpers;

public static class GlobalLeaksJsonEnricher
{
    public static void Enrich(DenunciasGestiona denuncia, string exportJson)
    {
        using var document = JsonDocument.Parse(exportJson);
        var root = document.RootElement;

        if (denuncia.Id_Denuncia == 0 &&
            root.TryGetProperty("progressive", out var progressiveElement) &&
            progressiveElement.TryGetInt32(out var progressive))
        {
            denuncia.Id_Denuncia = progressive;
        }

        if (root.TryGetProperty("creation_date", out var creationDateElement) &&
            DateTimeOffset.TryParse(creationDateElement.GetString(), out var creationDate))
        {
            denuncia.Fecha = creationDate.LocalDateTime;
        }

        if (root.TryGetProperty("status", out var statusElement) &&
            string.IsNullOrWhiteSpace(denuncia.Estado))
        {
            denuncia.Estado = statusElement.GetString() ?? string.Empty;
        }

        if (root.TryGetProperty("label", out var labelElement) &&
            string.IsNullOrWhiteSpace(denuncia.Etiqueta))
        {
            denuncia.Etiqueta = labelElement.GetString() ?? string.Empty;
        }

        if (root.TryGetProperty("identity_provided", out var identityProvidedElement) &&
            identityProvidedElement.ValueKind is JsonValueKind.True or JsonValueKind.False)
        {
            denuncia.Confidencial = !identityProvidedElement.GetBoolean();
            if (string.IsNullOrWhiteSpace(denuncia.Tipo_Denuncia))
            {
                denuncia.Tipo_Denuncia = denuncia.Confidencial
                    ? "Anónima"
                    : "No anónima, deseo identificarme";
            }
        }

        var answers = ReadAnswers(root);
        ApplyAnswers(denuncia, answers);
        MergeComments(denuncia, root);
    }

    private static Dictionary<string, string> ReadAnswers(JsonElement root)
    {
        var definitions = new Dictionary<string, FieldDefinition>(StringComparer.OrdinalIgnoreCase);
        var answersByLabel = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        if (!root.TryGetProperty("questionnaires", out var questionnaires) ||
            questionnaires.ValueKind != JsonValueKind.Array)
        {
            return answersByLabel;
        }

        foreach (var questionnaire in questionnaires.EnumerateArray())
        {
            if (questionnaire.TryGetProperty("steps", out var steps) &&
                steps.ValueKind == JsonValueKind.Array)
            {
                foreach (var step in steps.EnumerateArray())
                {
                    CollectDefinitions(step, definitions);
                }
            }

            if (!questionnaire.TryGetProperty("answers", out var answers) ||
                answers.ValueKind != JsonValueKind.Object)
            {
                continue;
            }

            foreach (var answerEntry in answers.EnumerateObject())
            {
                if (!definitions.TryGetValue(answerEntry.Name, out var definition))
                {
                    continue;
                }

                var resolved = ResolveAnswer(answerEntry.Value, definition);
                if (!string.IsNullOrWhiteSpace(resolved))
                {
                    answersByLabel[Normalize(definition.Label)] = resolved;
                }
            }
        }

        return answersByLabel;
    }

    private static void CollectDefinitions(JsonElement element, Dictionary<string, FieldDefinition> definitions)
    {
        if (element.ValueKind != JsonValueKind.Object)
        {
            return;
        }

        if (element.TryGetProperty("id", out var idElement) &&
            element.TryGetProperty("type", out var typeElement) &&
            element.TryGetProperty("label", out var labelElement))
        {
            var id = idElement.GetString();
            if (!string.IsNullOrWhiteSpace(id))
            {
                definitions[id] = new FieldDefinition(
                    labelElement.GetString() ?? id,
                    typeElement.GetString() ?? string.Empty,
                    ParseOptions(element));
            }
        }

        foreach (var property in element.EnumerateObject())
        {
            if (property.NameEquals("options"))
            {
                continue;
            }

            if (property.Value.ValueKind == JsonValueKind.Object)
            {
                CollectDefinitions(property.Value, definitions);
            }
            else if (property.Value.ValueKind == JsonValueKind.Array)
            {
                foreach (var child in property.Value.EnumerateArray())
                {
                    CollectDefinitions(child, definitions);
                }
            }
        }
    }

    private static Dictionary<string, string> ParseOptions(JsonElement element)
    {
        var options = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        if (!element.TryGetProperty("options", out var optionsElement) ||
            optionsElement.ValueKind != JsonValueKind.Array)
        {
            return options;
        }

        foreach (var option in optionsElement.EnumerateArray())
        {
            if (!option.TryGetProperty("id", out var idElement) ||
                !option.TryGetProperty("label", out var labelElement))
            {
                continue;
            }

            var id = idElement.GetString();
            if (string.IsNullOrWhiteSpace(id))
            {
                continue;
            }

            options[id] = labelElement.GetString() ?? string.Empty;
        }

        return options;
    }

    private static string ResolveAnswer(JsonElement answerArray, FieldDefinition definition)
    {
        if (answerArray.ValueKind == JsonValueKind.Object)
        {
            return ResolveAnswerObject(answerArray, definition);
        }

        if (answerArray.ValueKind != JsonValueKind.Array)
        {
            return ResolveValue(answerArray, definition.Options);
        }

        var values = new List<string>();

        foreach (var answer in answerArray.EnumerateArray())
        {
            var resolved = answer.ValueKind == JsonValueKind.Object
                ? ResolveAnswerObject(answer, definition)
                : ResolveValue(answer, definition.Options);
            if (!string.IsNullOrWhiteSpace(resolved))
            {
                values.Add(resolved);
            }
        }

        return string.Join("; ", values.Distinct(StringComparer.OrdinalIgnoreCase));
    }

    private static string ResolveAnswerObject(JsonElement answer, FieldDefinition definition)
    {
        var values = new List<string>();

        if (answer.TryGetProperty("value", out var valueElement))
        {
            var resolvedValue = ResolveValue(valueElement, definition.Options);
            if (!string.IsNullOrWhiteSpace(resolvedValue))
            {
                values.Add(resolvedValue);
            }
        }

        foreach (var property in answer.EnumerateObject())
        {
            if (property.NameEquals("value") || property.NameEquals("index") || property.NameEquals("required_status"))
            {
                continue;
            }

            if (property.Value.ValueKind is JsonValueKind.True or JsonValueKind.False &&
                property.Value.GetBoolean() &&
                definition.Options.TryGetValue(property.Name, out var label))
            {
                values.Add(label);
            }
        }

        return string.Join("; ", values.Distinct(StringComparer.OrdinalIgnoreCase));
    }

    private static string ResolveValue(JsonElement valueElement, Dictionary<string, string> options)
    {
        return valueElement.ValueKind switch
        {
            JsonValueKind.String => ResolveStringValue(valueElement.GetString(), options),
            JsonValueKind.True => "Sí",
            JsonValueKind.False => "No",
            JsonValueKind.Number => valueElement.GetRawText(),
            JsonValueKind.Array => ResolveArrayValue(valueElement, options),
            JsonValueKind.Object => ResolveObjectValue(valueElement, options),
            _ => string.Empty,
        };
    }

    private static string ResolveArrayValue(JsonElement valueElement, Dictionary<string, string> options)
    {
        var values = valueElement
            .EnumerateArray()
            .Select(item => ResolveValue(item, options))
            .Where(value => !string.IsNullOrWhiteSpace(value))
            .Distinct(StringComparer.OrdinalIgnoreCase);

        return string.Join("; ", values);
    }

    private static string ResolveObjectValue(JsonElement valueElement, Dictionary<string, string> options)
    {
        foreach (var propertyName in new[] { "label", "text", "value", "answer", "date", "formatted", "name" })
        {
            if (!valueElement.TryGetProperty(propertyName, out var propertyValue))
            {
                continue;
            }

            var resolved = ResolveValue(propertyValue, options);
            if (!string.IsNullOrWhiteSpace(resolved))
            {
                return resolved;
            }
        }

        if (TryReadDateParts(valueElement, out var dateText))
        {
            return dateText;
        }

        var values = new List<string>();
        foreach (var property in valueElement.EnumerateObject())
        {
            if (property.Value.ValueKind is JsonValueKind.True or JsonValueKind.False &&
                property.Value.GetBoolean() &&
                options.TryGetValue(property.Name, out var label))
            {
                values.Add(label);
            }
        }

        return string.Join("; ", values.Distinct(StringComparer.OrdinalIgnoreCase));
    }

    private static bool TryReadDateParts(JsonElement valueElement, out string dateText)
    {
        dateText = string.Empty;
        if (!TryGetIntProperty(valueElement, "day", out var day) ||
            !TryGetIntProperty(valueElement, "month", out var month) ||
            !TryGetIntProperty(valueElement, "year", out var year))
        {
            return false;
        }

        try
        {
            dateText = new DateTime(year, month, day).ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static bool TryGetIntProperty(JsonElement valueElement, string propertyName, out int value)
    {
        value = 0;
        if (!valueElement.TryGetProperty(propertyName, out var property))
        {
            return false;
        }

        return property.ValueKind switch
        {
            JsonValueKind.Number => property.TryGetInt32(out value),
            JsonValueKind.String => int.TryParse(property.GetString(), CultureInfo.InvariantCulture, out value),
            _ => false,
        };
    }

    private static string ResolveStringValue(string? rawValue, Dictionary<string, string> options)
    {
        if (string.IsNullOrWhiteSpace(rawValue))
        {
            return string.Empty;
        }

        return options.TryGetValue(rawValue, out var optionLabel)
            ? optionLabel
            : rawValue;
    }

    private static void ApplyAnswers(DenunciasGestiona denuncia, Dictionary<string, string> answers)
    {
        SetIfMissing(() => denuncia.TipoDenunciante, value => denuncia.TipoDenunciante = value, answers, "indique si actua como persona fisica o en representacion de una persona juridica");
        if (!string.IsNullOrWhiteSpace(denuncia.TipoDenunciante))
        {
            denuncia.EsPersonaJuridica = Normalize(denuncia.TipoDenunciante).Contains("juridica", StringComparison.OrdinalIgnoreCase);
        }

        SetIfMissing(() => denuncia.Asunto, value => denuncia.Asunto = value, answers, "asunto");
        SetIfMissing(() => denuncia.Nombre, value => denuncia.Nombre = value, answers, "nombre");
        SetIfMissing(() => denuncia.PrimerApellido, value => denuncia.PrimerApellido = value, answers, "1 apellido", "primer apellido");
        SetIfMissing(() => denuncia.SegundoApellido, value => denuncia.SegundoApellido = value, answers, "2 apellido", "segundo apellido");
        SetIfMissing(() => denuncia.Apellidos, value => denuncia.Apellidos = value, answers, "apellidos");
        SetIfMissing(() => denuncia.RazonSocial, value => denuncia.RazonSocial = value, answers, "razon social");
        SetIfMissing(() => denuncia.Sexo, value => denuncia.Sexo = value, answers, "sexo");
        SetIfMissing(() => denuncia.PaisOrigen, value => denuncia.PaisOrigen = value, answers, "pais de origen");
        SetIfMissing(() => denuncia.Dni, value => denuncia.Dni = value, answers, "nif dni nie", "dni", "nif", "nie", "cif", "otro documento identificativo");
        if (!string.IsNullOrWhiteSpace(denuncia.Dni) &&
            string.IsNullOrWhiteSpace(denuncia.TipoDocumentoIdentificativo))
        {
            denuncia.TipoDocumentoIdentificativo = "NIF (DNI, NIE)";
        }

        SetIfMissing(() => denuncia.A_Quien_Denuncia, value => denuncia.A_Quien_Denuncia = value, answers, "a quien denuncia");
        SetIfMissing(() => denuncia.DenunciadoDetalle, value => denuncia.DenunciadoDetalle = value, answers, "especifique a quien denuncia");
        SetIfMissing(() => denuncia.Descripcion_Denuncia, value => denuncia.Descripcion_Denuncia = value, answers, "describa su denuncia", "descripcion de la denuncia");
        SetIfMissing(() => denuncia.Denunciado_Ante_Inst, value => denuncia.Denunciado_Ante_Inst = value, answers, "ha denunciado estos hechos ante otras instituciones u organos");
        SetIfMissing(() => denuncia.OrganismoDenunciado, value => denuncia.OrganismoDenunciado = value, answers, "por favor indique el organismo o la institucion donde ha denunciado los hechos");
        SetIfMissing(() => denuncia.SolicitaProteccion, value => denuncia.SolicitaProteccion = value, answers, "solicita medidas concretas de proteccion");
        SetIfMissing(() => denuncia.MedidasProteccionSolicitadas, value => denuncia.MedidasProteccionSolicitadas = value, answers, "describa las medidas de proteccion solicitadas");
        SetIfMissing(() => denuncia.Lugar_Hechos, value => denuncia.Lugar_Hechos = value, answers, "lugar en el que ocurrieron los hechos que denuncia", "lugar en la que ocurrieron los hechos que denuncia", "lugar de los hechos");
        SetIfMissing(() => denuncia.AutorizaRemision, value => denuncia.AutorizaRemision = value, answers, "autorizacion para remitir su denuncia");
        SetIfMissing(() => denuncia.PreferenciaRemision, value => denuncia.PreferenciaRemision = value, answers, "en tal caso desea que su denuncia se remita anonimizada sin datos personales");
        SetIfMissing(() => denuncia.Notificacion_Preferencia, value => denuncia.Notificacion_Preferencia = value, answers, "preferencia de notificacion", "seleccione su preferencia de notificacion y seguimiento de su denuncia", "preferencia de notificacion y seguimiento de su denuncia", "notificaciones");
        SetIfMissing(() => denuncia.Notificacion_Electronica, value => denuncia.Notificacion_Electronica = value, answers, "notificaciones electronicas");
        SetIfMissing(() => denuncia.SeguimientoOnline, value => denuncia.SeguimientoOnline = value, answers, "seguimiento online", "seguimiento de su denuncia");
        SetIfMissing(() => denuncia.Notificacion_Sms, value => denuncia.Notificacion_Sms = value, answers, "autorizo recibir notificaciones via sms", "autorizacion notificaciones via sms", "autoriza notificaciones via sms", "notificaciones via sms", "sms");
        SetIfMissing(() => denuncia.NotificacionPostal, value => denuncia.NotificacionPostal = value, answers, "autorizo recibir notificaciones via correo postal");
        SetIfMissing(() => denuncia.Correo_Electronico, value => denuncia.Correo_Electronico = value, answers, "correo electronico", "email");
        SetIfMissing(() => denuncia.Telefono, value => denuncia.Telefono = value, answers, "contacto telefonico", "telefono", "telefono movil");
        SetIfMissing(() => denuncia.Direccion, value => denuncia.Direccion = value, answers, "nombre de la via", "direccion", "domicilio");
        SetIfMissing(() => denuncia.DireccionTipoVia, value => denuncia.DireccionTipoVia = value, answers, "tipo de via");
        SetIfMissing(() => denuncia.DireccionNumero, value => denuncia.DireccionNumero = value, answers, "numero", "numero km");
        SetIfMissing(() => denuncia.DireccionPiso, value => denuncia.DireccionPiso = value, answers, "piso", "planta");
        SetIfMissing(() => denuncia.DireccionPuerta, value => denuncia.DireccionPuerta = value, answers, "puerta");
        SetIfMissing(() => denuncia.DireccionBloque, value => denuncia.DireccionBloque = value, answers, "bloque");
        SetIfMissing(() => denuncia.DireccionEscalera, value => denuncia.DireccionEscalera = value, answers, "escalera");
        SetIfMissing(() => denuncia.DireccionExtra, value => denuncia.DireccionExtra = value, answers, "extra");
        SetIfMissing(() => denuncia.Municipio, value => denuncia.Municipio = value, answers, "municipio", "localidad", "poblacion");
        SetIfMissing(() => denuncia.Provincia, value => denuncia.Provincia = value, answers, "provincia");
        SetIfMissing(() => denuncia.CodigoPostal, value => denuncia.CodigoPostal = value, answers, "codigo postal", "c p");
        SetIfMissing(() => denuncia.Pais, value => denuncia.Pais = value, answers, "pais");

        if (denuncia.Fecha_Hechos == DateTime.MinValue &&
            TryGetAnswer(answers, out var fechaHechos, "fecha de los hechos que denuncia", "fecha de los hechos") &&
            TryParseReportDate(fechaHechos, out var parsedDate))
        {
            denuncia.Fecha_Hechos = parsedDate;
        }

        if (string.IsNullOrWhiteSpace(denuncia.Apellidos))
        {
            denuncia.Apellidos = string.Join(
                ' ',
                new[] { denuncia.PrimerApellido, denuncia.SegundoApellido }
                    .Where(value => !string.IsNullOrWhiteSpace(value)));
        }
    }

    private static void MergeComments(DenunciasGestiona denuncia, JsonElement root)
    {
        if (!root.TryGetProperty("comments", out var commentsElement) ||
            commentsElement.ValueKind != JsonValueKind.Array)
        {
            return;
        }

        var builder = new StringBuilder();

        foreach (var comment in commentsElement.EnumerateArray())
        {
            if (!comment.TryGetProperty("content", out var contentElement))
            {
                continue;
            }

            var content = contentElement.GetString();
            if (string.IsNullOrWhiteSpace(content))
            {
                continue;
            }

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

            builder.Append(content.Trim());
        }

        var merged = builder.ToString().Trim();
        if (!string.IsNullOrWhiteSpace(merged) && !denuncia.Comments.Contains(merged, StringComparison.OrdinalIgnoreCase))
        {
            denuncia.Comments = string.IsNullOrWhiteSpace(denuncia.Comments)
                ? merged
                : $"{denuncia.Comments}{Environment.NewLine}{Environment.NewLine}{merged}";
        }
    }

    private static void SetIfMissing(
        Func<string> currentValue,
        Action<string> assign,
        Dictionary<string, string> answers,
        params string[] candidateLabels)
    {
        if (!string.IsNullOrWhiteSpace(currentValue()))
        {
            return;
        }

        if (TryGetAnswer(answers, out var value, candidateLabels))
        {
            assign(value);
        }
    }

    private static bool TryGetAnswer(
        Dictionary<string, string> answers,
        out string value,
        params string[] candidateLabels)
    {
        foreach (var label in candidateLabels)
        {
            if (answers.TryGetValue(Normalize(label), out value!))
            {
                return true;
            }
        }

        value = string.Empty;
        return false;
    }

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

        foreach (var culture in new[]
                 {
                     CultureInfo.GetCultureInfo("es-ES"),
                     CultureInfo.InvariantCulture,
                     CultureInfo.CurrentCulture
                 })
        {
            if (DateTime.TryParse(value, culture, DateTimeStyles.None, out parsedDate))
            {
                return true;
            }
        }

        foreach (var format in new[] { "dd/MM/yyyy", "d/M/yyyy", "yyyy-MM-dd" })
        {
            if (DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate))
            {
                return true;
            }
        }

        return false;
    }

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

        var normalized = text.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 string.Join(' ', builder.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries));
    }

    private sealed record FieldDefinition(string Label, string Type, Dictionary<string, string> Options);
}
