using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using ApiDenuncias.Services;
using ApiDenuncias.Security;
using GestionaDenuncias.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ApiDenuncias.Controllers;

[ApiController]
[Authorize]
[Route("api/denuncias")]
public sealed class DenunciasController : ControllerBase
{
    private readonly IDenunciaStore _denunciaStore;
    private readonly IFilteredDenunciaStore _filteredDenunciaStore;
    private readonly UserComplaintAccessService _accessService;
    private readonly IInboxTrackingService _trackingService;
    private readonly ILogger<DenunciasController> _logger;

    public DenunciasController(
        IDenunciaStore denunciaStore,
        IFilteredDenunciaStore filteredDenunciaStore,
        UserComplaintAccessService accessService,
        IInboxTrackingService trackingService,
        ILogger<DenunciasController> logger)
    {
        _denunciaStore = denunciaStore;
        _filteredDenunciaStore = filteredDenunciaStore;
        _accessService = accessService;
        _trackingService = trackingService;
        _logger = logger;
    }

    [HttpPost("schema/ensure")]
    public async Task<IActionResult> EnsureSchema(CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet]
    public async Task<ActionResult<List<DenunciasGestiona>>> GetAll(
        [FromQuery] DenunciaListScope scope,
        CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        var access = await _accessService.GetComplaintAccessAsync(
            GetUsername(),
            null,
            cancellationToken);
        if (access.Count == 0)
        {
            return Ok(new List<DenunciasGestiona>());
        }

        var complaints = await _filteredDenunciaStore.GetDenunciasByIdsAsync(
            access.Keys.ToArray(),
            scope,
            cancellationToken);
        ApplyAccessMetadata(complaints, access);
        return Ok(complaints);
    }

    [HttpGet("{denunciaId:int}")]
    public async Task<ActionResult<DenunciasGestiona?>> GetById(int denunciaId, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        var complaint = await _denunciaStore.GetDenunciaByIdAsync(denunciaId, cancellationToken);
        if (complaint is null)
        {
            return Ok(null);
        }

        var access = await _accessService.GetComplaintAccessAsync(
            GetUsername(),
            [denunciaId],
            cancellationToken);
        ApplyAccessMetadata([complaint], access);
        return Ok(complaint);
    }

    [AllowAnonymous]
    [ServiceFilter(typeof(OpeBridgeApiKeyFilter))]
    [HttpGet("{denunciaId:int}/gestiona-fields")]
    public async Task<ActionResult<GestionaExternalFieldsResponse>> GetGestionaFields(
        int denunciaId,
        CancellationToken cancellationToken)
    {
        if (denunciaId <= 0)
        {
            return BadRequest(new ApiError("Debes indicar un numero de denuncia valido."));
        }

        var denuncia = await _denunciaStore.GetDenunciaByIdAsync(denunciaId, cancellationToken);
        if (denuncia is null)
        {
            return NotFound(new ApiError("No se ha encontrado la denuncia solicitada."));
        }

        return BuildGestionaFieldsResponse(denuncia);
    }

    [AllowAnonymous]
    [ServiceFilter(typeof(OpeBridgeApiKeyFilter))]
    [HttpGet("{numeroExpediente:int}/{anioExpediente:int}/gestiona-fields")]
    public async Task<ActionResult<GestionaExternalFieldsResponse>> GetGestionaFieldsByExpediente(
        int numeroExpediente,
        int anioExpediente,
        CancellationToken cancellationToken)
    {
        if (numeroExpediente <= 0 || anioExpediente <= 0)
        {
            return BadRequest(new ApiError("Debes indicar un numero de expediente de Gestiona valido."));
        }

        var expedienteGestiona = $"{numeroExpediente.ToString(CultureInfo.InvariantCulture)}/{anioExpediente.ToString(CultureInfo.InvariantCulture)}";
        var denuncia = await _filteredDenunciaStore.GetDenunciaByGestionaFileCodeAsync(
            expedienteGestiona,
            cancellationToken);
        if (denuncia is null)
        {
            return NotFound(new ApiError("No se ha encontrado una denuncia asociada al expediente de Gestiona solicitado."));
        }

        return BuildGestionaFieldsResponse(denuncia);
    }

    private ActionResult<GestionaExternalFieldsResponse> BuildGestionaFieldsResponse(DenunciasGestiona denuncia)
    {
        var response = ToGestionaComplaintFields(denuncia);
        var missingFields = GetMissingGestionaFields(response);
        if (missingFields.Count > 0)
        {
            _logger.LogInformation(
                "Campos Gestiona denuncia {DenunciaId}: campos vacios={MissingFields}; rawReportLength={RawReportLength}; formFieldsCount={FormFieldsCount}.",
                denuncia.Id_Denuncia,
                string.Join(", ", missingFields),
                denuncia.TextoOriginalReport?.Length ?? 0,
                denuncia.GetCamposFormulario().Count);
        }

        return Ok(ToExternalGestionaFields(response));
    }

    [HttpPost]
    public async Task<IActionResult> Upsert(DenunciasGestiona denuncia, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denuncia.Id_Denuncia, cancellationToken))
        {
            return Forbid();
        }

        await _denunciaStore.UpsertDenunciaAsync(denuncia, cancellationToken);
        await TryRegisterGestionaHistoryFromComplaintAsync(denuncia, cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet("ficheros")]
    public async Task<ActionResult<List<FicherosDenuncias>>> GetAllFicheros(CancellationToken cancellationToken)
    {
        var allowedIds = await GetAllowedIdsAsync(cancellationToken);
        if (allowedIds.Count == 0)
        {
            return Ok(new List<FicherosDenuncias>());
        }

        return Ok(await _filteredDenunciaStore.GetFicherosByDenunciaIdsAsync(allowedIds, cancellationToken));
    }

    [HttpGet("{denunciaId:int}/ficheros")]
    public async Task<ActionResult<List<FicherosDenuncias>>> GetFicherosByDenuncia(int denunciaId, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        return Ok(await _denunciaStore.GetFicherosByDenunciaAsync(denunciaId, cancellationToken));
    }

    [HttpGet("gestiona-history")]
    public async Task<ActionResult<List<GestionaUploadHistoryEntry>>> GetGestionaHistory(CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return Ok(await _denunciaStore.GetGestionaUploadHistoryAsync(cancellationToken));
    }

    [HttpPost("gestiona-history")]
    public async Task<IActionResult> AddGestionaHistory(
        GestionaUploadHistoryCreateRequest request,
        CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(request.DenunciaId, cancellationToken))
        {
            return Forbid();
        }

        await _denunciaStore.AddGestionaUploadHistoryAsync(request, GetUsername(), cancellationToken);
        await _trackingService.MarkReportHandledInGestionaAsync(
            GetUsername(),
            request.DenunciaId,
            request.UploadedAtUtc,
            cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet("{denunciaId:int}/ficheros/content")]
    public async Task<IActionResult> GetFicheroContent(
        int denunciaId,
        [FromQuery] string fileName,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return BadRequest(new ApiError("Nombre de fichero obligatorio."));
        }

        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        var ficheros = await _denunciaStore.GetFicherosByDenunciaAsync(denunciaId, cancellationToken);
        var fichero = ficheros.FirstOrDefault(file =>
            string.Equals(file.NombreFichero, fileName, StringComparison.Ordinal));

        if (fichero?.Fichero is not { Length: > 0 } bytes)
        {
            return NotFound();
        }

        return File(
            bytes,
            GetAttachmentContentType(fichero.NombreFichero),
            enableRangeProcessing: true);
    }

    [HttpPost("ficheros")]
    public async Task<IActionResult> UpsertFicheros(UpsertFicherosRequest request, CancellationToken cancellationToken)
    {
        var ficheros = request.Ficheros ?? [];
        foreach (var denunciaId in ficheros.Select(f => f.Id_Denuncia).Distinct())
        {
            if (!await CanAccessAsync(denunciaId, cancellationToken))
            {
                return Forbid();
            }
        }

        await _denunciaStore.UpsertFicherosAsync(ficheros, cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpPost("{denunciaId:int}/ficheros/mark-uploaded")]
    public async Task<IActionResult> MarkFicherosAsUploaded(
        int denunciaId,
        MarkFicherosUploadedRequest request,
        CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return Forbid();
        }

        await _denunciaStore.MarkFicherosAsUploadedAsync(
            denunciaId,
            request.FileNames,
            request.UploadedAtUtc,
            cancellationToken);

        return Ok(new { ok = true });
    }

    private async Task<HashSet<int>> GetAllowedIdsAsync(CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return await _accessService.GetAllowedComplaintIdsAsync(GetUsername(), cancellationToken);
    }

    private async Task<bool> CanAccessAsync(int denunciaId, CancellationToken cancellationToken)
    {
        if (denunciaId <= 0)
        {
            return false;
        }

        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return await _accessService.CanAccessComplaintAsync(GetUsername(), denunciaId, cancellationToken);
    }

    private string GetUsername()
        => User.Identity?.Name ?? throw new InvalidOperationException("No hay usuario autenticado.");

    private static void ApplyAccessMetadata(
        IEnumerable<DenunciasGestiona> complaints,
        IReadOnlyDictionary<int, ComplaintAccessInfo> access)
    {
        foreach (var complaint in complaints)
        {
            if (!access.TryGetValue(complaint.Id_Denuncia, out var item))
            {
                continue;
            }

            complaint.OwnerUsername = item.OwnerUsername;
            complaint.OwnedByCurrentUser = item.OwnedByCurrentUser;
            complaint.RequiresOwnerConfirmation = item.RequiresOwnerConfirmation;
            complaint.OwnerWorkGroups = item.OwnerGroupCodes;
        }
    }

    private static GestionaComplaintFieldsResponse ToGestionaComplaintFields(DenunciasGestiona denuncia)
    {
        var preferenciaNotificacion = ResolveNotificationPreference(denuncia);
        var seguimiento = ResolveSeguimientoDenuncia(denuncia);
        var sms = ResolveSmsNotification(denuncia);

        return new GestionaComplaintFieldsResponse(
            FechaDenuncia: ToNullableDate(denuncia.Fecha),
            NumeroDenunciaCanal: denuncia.Id_Denuncia,
            AQuienDenuncia: ResolveReportField(denuncia, denuncia.A_Quien_Denuncia, "a quien denuncia"),
            ResumenDenuncia: FirstNonEmpty(
                ResolveReportField(denuncia, string.Empty, "resumen de la denuncia", "resumen denuncia"),
                ResolveReportField(denuncia, denuncia.Descripcion_Denuncia, "describa su denuncia", "descripcion de la denuncia")),
            FechaHechos: ResolveFechaHechos(denuncia),
            LugarHechos: ResolveLugarHechos(denuncia),
            AmbitoCompetencias: ResolveAmbitoCompetencias(denuncia),
            SolicitaProteccion: ResolveReportField(denuncia, denuncia.SolicitaProteccion, "solicita medidas concretas de proteccion", "solicita proteccion"),
            SexoDenunciante: ResolveReportField(denuncia, denuncia.Sexo, "sexo"),
            AutorizaRemisionDenuncia: ResolveReportField(denuncia, denuncia.AutorizaRemision, "autorizacion para remitir su denuncia", "autoriza remision de la denuncia"),
            AutorizaNotificacionesViaSms: sms,
            PreferenciaNotificacionSeguimientoDenuncia: JoinDistinct(preferenciaNotificacion, seguimiento));
    }

    private static GestionaExternalFieldsResponse ToExternalGestionaFields(GestionaComplaintFieldsResponse source)
    {
        return new GestionaExternalFieldsResponse(
            new Dictionary<string, GestionaExternalFieldValue>(StringComparer.Ordinal)
            {
                ["fechaDenuncia"] = StringField(source.FechaDenuncia?.ToString("O", CultureInfo.InvariantCulture) ?? string.Empty),
                ["numeroDenunciaCanal"] = StringField(source.NumeroDenunciaCanal.ToString(CultureInfo.InvariantCulture)),
                ["aQuienDenuncia"] = StringField(source.AQuienDenuncia),
                ["resumenDenuncia"] = StringField(source.ResumenDenuncia),
                ["fechaHechos"] = StringField(source.FechaHechos),
                ["lugarHechos"] = StringField(source.LugarHechos),
                ["ambitoCompetencias"] = StringField(source.AmbitoCompetencias),
                ["solicitaProteccion"] = StringField(source.SolicitaProteccion),
                ["sexoDenunciante"] = StringField(source.SexoDenunciante),
                ["autorizaRemisionDenuncia"] = StringField(source.AutorizaRemisionDenuncia),
                ["autorizaNotificacionesViaSms"] = StringField(source.AutorizaNotificacionesViaSms),
                ["preferenciaNotificacionSeguimientoDenuncia"] = StringField(source.PreferenciaNotificacionSeguimientoDenuncia)
            });
    }

    private static GestionaExternalFieldValue StringField(string? value)
        => new("STRING", value?.Trim() ?? string.Empty);

    private async Task TryRegisterGestionaHistoryFromComplaintAsync(
        DenunciasGestiona denuncia,
        CancellationToken cancellationToken)
    {
        if (!denuncia.EnGestiona || denuncia.FechaSubidaAGestiona == DateTime.MinValue)
        {
            return;
        }

        try
        {
            await _denunciaStore.AddGestionaUploadHistoryAsync(
                new GestionaUploadHistoryCreateRequest(
                    denuncia.Id_Denuncia,
                    denuncia.Expediente_Gestiona ?? string.Empty,
                    denuncia.ExpedienteGestionaMostrable,
                    denuncia.UltimaSubidaGestionaTipoMostrable,
                    denuncia.UltimoGrupoAsignadoGestionaMostrable,
                    denuncia.FechaSubidaAGestiona,
                    denuncia.NombreDenuncia ?? string.Empty,
                    denuncia.ArchivoElegido ?? string.Empty),
                GetUsername(),
                cancellationToken);
            await _trackingService.MarkReportHandledInGestionaAsync(
                GetUsername(),
                denuncia.Id_Denuncia,
                denuncia.FechaSubidaAGestiona,
                cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(
                ex,
                "No se ha podido registrar el historico de subida a Gestiona para la denuncia {DenunciaId}.",
                denuncia.Id_Denuncia);
        }
    }

    private static DateTime? ToNullableDate(DateTime value)
        => value == DateTime.MinValue ? null : value;

    private static IReadOnlyList<string> GetMissingGestionaFields(GestionaComplaintFieldsResponse response)
    {
        var missing = new List<string>();

        AddIfMissing(missing, response.AQuienDenuncia, nameof(response.AQuienDenuncia));
        AddIfMissing(missing, response.ResumenDenuncia, nameof(response.ResumenDenuncia));
        AddIfMissing(missing, response.FechaHechos, nameof(response.FechaHechos));
        AddIfMissing(missing, response.LugarHechos, nameof(response.LugarHechos));
        AddIfMissing(missing, response.AmbitoCompetencias, nameof(response.AmbitoCompetencias));
        AddIfMissing(missing, response.SolicitaProteccion, nameof(response.SolicitaProteccion));
        AddIfMissing(missing, response.SexoDenunciante, nameof(response.SexoDenunciante));
        AddIfMissing(missing, response.AutorizaRemisionDenuncia, nameof(response.AutorizaRemisionDenuncia));
        AddIfMissing(missing, response.AutorizaNotificacionesViaSms, nameof(response.AutorizaNotificacionesViaSms));
        AddIfMissing(missing, response.PreferenciaNotificacionSeguimientoDenuncia, nameof(response.PreferenciaNotificacionSeguimientoDenuncia));

        return missing;
    }

    private static void AddIfMissing(List<string> missing, string? value, string fieldName)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            missing.Add(fieldName);
        }
    }

    private static string ResolveFechaHechos(DenunciasGestiona denuncia)
    {
        var literalValue = ResolveReportField(
            denuncia,
            string.Empty,
            "fecha de los hechos que denuncia",
            "fecha de los hechos");

        if (!string.IsNullOrWhiteSpace(literalValue))
        {
            return literalValue;
        }

        var textDate = ExtractDateAfterReportLabel(
            denuncia.TextoOriginalReport,
            normalizedLabel => normalizedLabel.Contains("fecha", StringComparison.Ordinal) &&
                               normalizedLabel.Contains("hechos", StringComparison.Ordinal));
        if (!string.IsNullOrWhiteSpace(textDate))
        {
            return textDate;
        }

        return denuncia.Fecha_Hechos == DateTime.MinValue
            ? string.Empty
            : denuncia.Fecha_Hechos.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
    }

    private static string ResolveLugarHechos(DenunciasGestiona denuncia)
    {
        var directValue = ResolveReportField(
            denuncia,
            denuncia.Lugar_Hechos,
            "lugar en el que ocurrieron los hechos que denuncia",
            "lugar en la que ocurrieron los hechos que denuncia",
            "lugar de los hechos");

        if (!string.IsNullOrWhiteSpace(directValue))
        {
            return directValue;
        }

        return ExtractReportTextValue(
            denuncia.TextoOriginalReport,
            normalizedLabel => normalizedLabel.Contains("lugar", StringComparison.Ordinal) &&
                               normalizedLabel.Contains("hechos", StringComparison.Ordinal));
    }

    private static string ResolveSeguimientoDenuncia(DenunciasGestiona denuncia)
    {
        var directValue = ResolveReportField(
            denuncia,
            denuncia.SeguimientoOnline,
            "seguimiento online",
            "seguimiento de su denuncia");

        if (!string.IsNullOrWhiteSpace(directValue))
        {
            return directValue;
        }

        return ReportContainsLabel(denuncia.TextoOriginalReport, "seguimiento online")
            ? "Seguimiento Online"
            : string.Empty;
    }

    private static string ResolveNotificationPreference(DenunciasGestiona denuncia)
    {
        var directValue = ResolveReportField(
            denuncia,
            denuncia.Notificacion_Preferencia,
            "preferencia de notificacion",
            "seleccione su preferencia de notificacion y seguimiento de su denuncia",
            "preferencia de notificacion y seguimiento de su denuncia",
            "notificaciones");
        if (!string.IsNullOrWhiteSpace(directValue))
        {
            return directValue;
        }

        directValue = ResolveReportFieldAllowingLabelValues(
            denuncia,
            string.Empty,
            "preferencia de notificacion",
            "seleccione su preferencia de notificacion y seguimiento de su denuncia",
            "preferencia de notificacion y seguimiento de su denuncia");
        if (!string.IsNullOrWhiteSpace(directValue))
        {
            return directValue;
        }

        var electronic = ResolveReportField(
            denuncia,
            denuncia.Notificacion_Electronica,
            "notificaciones electronicas",
            "notificacion electronica");
        if (!string.IsNullOrWhiteSpace(electronic) ||
            ReportContainsLabel(denuncia.TextoOriginalReport, "notificaciones electronicas"))
        {
            return "Notificaciones electronicas";
        }

        var postal = ResolveReportField(
            denuncia,
            denuncia.NotificacionPostal,
            "autorizo recibir notificaciones via correo postal",
            "notificaciones via correo postal",
            "correo postal");
        return string.IsNullOrWhiteSpace(postal) ? string.Empty : "Correo postal";
    }

    private static string ResolveSmsNotification(DenunciasGestiona denuncia)
    {
        return ResolveReportField(
            denuncia,
            denuncia.Notificacion_Sms,
            "autorizo recibir notificaciones via sms",
            "autorizacion notificaciones via sms",
            "autorizacion para recibir notificaciones via sms",
            "autoriza notificaciones via sms",
            "autorizo recibir notificaciones sms",
            "notificaciones via sms",
            "sms");
    }

    private static string ResolveAmbitoCompetencias(DenunciasGestiona denuncia)
    {
        var directValue = ResolveReportField(
            denuncia,
            FirstNonEmpty(denuncia.Modalidad_Informacion, denuncia.Asunto),
            "asunto",
            "categoria",
            "tipo de denuncia",
            "ambito de competencias",
            "ambito competencial",
            "ambito",
            "competencias",
            "modalidad de informacion",
            "modalidad informacion");

        if (!string.IsNullOrWhiteSpace(directValue))
        {
            return directValue;
        }

        return FindReportFieldValue(denuncia, normalizedLabel =>
            normalizedLabel.Contains("ambito", StringComparison.Ordinal) &&
            normalizedLabel.Contains("compet", StringComparison.Ordinal));
    }

    private static string ResolveReportField(
        DenunciasGestiona denuncia,
        string? currentValue,
        params string[] candidateLabels)
    {
        if (!string.IsNullOrWhiteSpace(currentValue))
        {
            return currentValue.Trim();
        }

        var labels = candidateLabels
            .Select(NormalizeLabel)
            .Where(label => !string.IsNullOrWhiteSpace(label))
            .ToHashSet(StringComparer.Ordinal);

        if (labels.Count == 0)
        {
            return string.Empty;
        }

        foreach (var field in denuncia.GetCamposFormulario())
        {
            if (!string.IsNullOrWhiteSpace(field.Value) &&
                labels.Contains(NormalizeLabel(field.Label)))
            {
                return field.Value.Trim();
            }
        }

        return ExtractReportTextValue(denuncia.TextoOriginalReport, labels);
    }

    private static string ResolveReportFieldAllowingLabelValues(
        DenunciasGestiona denuncia,
        string? currentValue,
        params string[] candidateLabels)
    {
        if (!string.IsNullOrWhiteSpace(currentValue))
        {
            return currentValue.Trim();
        }

        var labels = candidateLabels
            .Select(NormalizeLabel)
            .Where(label => !string.IsNullOrWhiteSpace(label))
            .ToHashSet(StringComparer.Ordinal);

        if (labels.Count == 0)
        {
            return string.Empty;
        }

        foreach (var field in denuncia.GetCamposFormulario())
        {
            if (!string.IsNullOrWhiteSpace(field.Value) &&
                labels.Contains(NormalizeLabel(field.Label)))
            {
                return field.Value.Trim();
            }
        }

        return ExtractReportTextValue(denuncia.TextoOriginalReport, labels, stopAtLikelyLabels: false);
    }

    private static string FindReportFieldValue(
        DenunciasGestiona denuncia,
        Func<string, bool> labelPredicate)
    {
        foreach (var field in denuncia.GetCamposFormulario())
        {
            if (!string.IsNullOrWhiteSpace(field.Value) &&
                labelPredicate(NormalizeLabel(field.Label)))
            {
                return field.Value.Trim();
            }
        }

        return string.Empty;
    }

    private static string ExtractDateAfterReportLabel(string? reportText, Func<string, bool> labelPredicate)
    {
        if (string.IsNullOrWhiteSpace(reportText))
        {
            return string.Empty;
        }

        var lines = reportText
            .Replace("\r\n", "\n", StringComparison.Ordinal)
            .Replace('\r', '\n')
            .Split('\n')
            .Select(line => line.Trim())
            .ToArray();

        for (var index = 0; index < lines.Length; index++)
        {
            var normalized = NormalizeLabel(lines[index]);
            if (!labelPredicate(normalized))
            {
                continue;
            }

            for (var valueIndex = index + 1; valueIndex < lines.Length && valueIndex <= index + 12; valueIndex++)
            {
                var candidate = lines[valueIndex].Trim();
                if (string.IsNullOrWhiteSpace(candidate) || IsReportStructuralLine(candidate))
                {
                    continue;
                }

                var dateMatch = Regex.Match(
                    candidate,
                    @"\b\d{1,2}/\d{1,2}/\d{4}\b",
                    RegexOptions.CultureInvariant);
                if (dateMatch.Success)
                {
                    return dateMatch.Value;
                }

                if (IsLikelyReportLabel(candidate))
                {
                    break;
                }
            }
        }

        var rawMatch = Regex.Match(
            reportText,
            @"fecha\s+de\s+los\s+hechos[\s\S]{0,600}?(\b\d{1,2}/\d{1,2}/\d{4}\b)",
            RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
        return rawMatch.Success ? rawMatch.Groups[1].Value : string.Empty;
    }

    private static string ExtractReportTextValue(string? reportText, IReadOnlySet<string> normalizedLabels)
        => ExtractReportTextValue(
            reportText,
            normalizedLabel => normalizedLabels.Contains(normalizedLabel));

    private static string ExtractReportTextValue(
        string? reportText,
        IReadOnlySet<string> normalizedLabels,
        bool stopAtLikelyLabels)
        => ExtractReportTextValue(
            reportText,
            normalizedLabel => normalizedLabels.Contains(normalizedLabel),
            stopAtLikelyLabels);

    private static string ExtractReportTextValue(string? reportText, Func<string, bool> labelPredicate)
        => ExtractReportTextValue(reportText, labelPredicate, stopAtLikelyLabels: true);

    private static string ExtractReportTextValue(
        string? reportText,
        Func<string, bool> labelPredicate,
        bool stopAtLikelyLabels)
    {
        if (string.IsNullOrWhiteSpace(reportText))
        {
            return string.Empty;
        }

        var lines = reportText
            .Replace("\r\n", "\n", StringComparison.Ordinal)
            .Replace('\r', '\n')
            .Split('\n')
            .Select(line => line.Trim())
            .ToArray();

        for (var index = 0; index < lines.Length; index++)
        {
            if (!labelPredicate(NormalizeLabel(lines[index])))
            {
                continue;
            }

            for (var valueIndex = index + 1; valueIndex < lines.Length; valueIndex++)
            {
                var candidate = lines[valueIndex].Trim();
                if (string.IsNullOrWhiteSpace(candidate) || IsReportStructuralLine(candidate))
                {
                    continue;
                }

                if (stopAtLikelyLabels && IsLikelyReportLabel(candidate))
                {
                    break;
                }

                return candidate;
            }
        }

        return string.Empty;
    }

    private static bool ReportContainsLabel(string? reportText, string label)
    {
        if (string.IsNullOrWhiteSpace(reportText))
        {
            return false;
        }

        var normalizedLabel = NormalizeLabel(label);
        return reportText
            .Replace("\r\n", "\n", StringComparison.Ordinal)
            .Replace('\r', '\n')
            .Split('\n')
            .Any(line => string.Equals(NormalizeLabel(line.Trim()), normalizedLabel, StringComparison.Ordinal));
    }

    private static bool IsReportStructuralLine(string value)
    {
        if (Regex.IsMatch(value, @"^\d+/\d+$", RegexOptions.CultureInvariant))
        {
            return true;
        }

        return value.StartsWith("REPORT ", StringComparison.OrdinalIgnoreCase) ||
               value.Equals("[CONFIDENTIAL]", StringComparison.OrdinalIgnoreCase) ||
               value.Equals("{Messages}", StringComparison.OrdinalIgnoreCase) ||
               value.StartsWith("Comments", StringComparison.OrdinalIgnoreCase) ||
               value.StartsWith("De:", StringComparison.OrdinalIgnoreCase) ||
               value.StartsWith("Fecha:", StringComparison.OrdinalIgnoreCase);
    }

    private static bool IsLikelyReportLabel(string value)
    {
        var normalized = NormalizeLabel(value);
        if (string.IsNullOrWhiteSpace(normalized))
        {
            return false;
        }

        return normalized is "descripcion"
                   or "datos del denunciante"
                   or "condiciones y reglas de uso"
                   or "tratamiento de datos personales"
                   or "preferencias de notificacion"
                   or "notificaciones electronicas" ||
               normalized.StartsWith("indique ", StringComparison.Ordinal) ||
               normalized.StartsWith("describa ", StringComparison.Ordinal) ||
               normalized.StartsWith("autoriza ", StringComparison.Ordinal) ||
               normalized.StartsWith("autorizacion ", StringComparison.Ordinal) ||
               normalized.StartsWith("ha denunciado ", StringComparison.Ordinal) ||
               normalized.StartsWith("solicita ", StringComparison.Ordinal) ||
               normalized.StartsWith("por favor ", StringComparison.Ordinal) ||
               normalized.Contains(" denuncia", StringComparison.Ordinal) ||
               normalized.Contains(" hechos", StringComparison.Ordinal) ||
               normalized.Contains(" notificacion", StringComparison.Ordinal);
    }

    private static string JoinDistinct(params string[] values)
    {
        var result = new List<string>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var value in values)
        {
            var trimmed = value?.Trim();
            if (!string.IsNullOrWhiteSpace(trimmed) && seen.Add(trimmed))
            {
                result.Add(trimmed);
            }
        }

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

    private static string FirstNonEmpty(params string[] values)
    {
        foreach (var value in values)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                return value.Trim();
            }
        }

        return string.Empty;
    }
    //pruabass
    private static string NormalizeLabel(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

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

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

            if (ch is '\u00BA' or '\u00AA')
            {
                continue;
            }

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

        return string.Join(
            ' ',
            builder
                .ToString()
                .Normalize(NormalizationForm.FormC)
                .Split(' ', StringSplitOptions.RemoveEmptyEntries));
    }

    private static string GetAttachmentContentType(string? fileName)
    {
        return Path.GetExtension(fileName ?? string.Empty).ToLowerInvariant() switch
        {
            ".pdf" => "application/pdf",
            ".txt" => "text/plain",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".png" => "image/png",
            ".gif" => "image/gif",
            ".zip" => "application/zip",
            _ => "application/octet-stream"
        };
    }
}
