using System.IO.Compression;
using System.Text;
using ApiDenuncias.Helpers;
using GestionaDenuncias.Shared.Models;

namespace ApiDenuncias.Services;

public sealed class DenunciaInboxService
{
    private const string RootPath = @"C:\ZipsDenuncias";

    private readonly IGestionaService _gestionaService;
    private readonly IDenunciaStore _denunciaStore;
    private readonly ILogger<DenunciaInboxService> _logger;

    public DenunciaInboxService(
        IGestionaService gestionaService,
        IDenunciaStore denunciaStore,
        ILogger<DenunciaInboxService> logger)
    {
        _gestionaService = gestionaService;
        _denunciaStore = denunciaStore;
        _logger = logger;
    }

    public async Task EnsureStorageReadyAsync(CancellationToken cancellationToken = default)
    {
        Directory.CreateDirectory(RootPath);
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
    }

    public async Task<IReadOnlyList<string>> GetExistingZipNamesAsync(CancellationToken cancellationToken = default)
    {
        await EnsureStorageReadyAsync(cancellationToken);
        return Directory
            .GetFiles(RootPath, "*.zip")
            .Select(Path.GetFileName)
            .Where(name => !string.IsNullOrWhiteSpace(name))
            .OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
            .ToArray()!;
    }

    public async Task DeleteZipAsync(string zipName, CancellationToken cancellationToken = default)
    {
        await EnsureStorageReadyAsync(cancellationToken);

        var fullPath = Path.Combine(RootPath, zipName);
        if (File.Exists(fullPath))
        {
            File.Delete(fullPath);
        }
    }

    public async Task<ImportSummary> ProcessPendingFolderZipsAsync(CancellationToken cancellationToken = default)
    {
        await EnsureStorageReadyAsync(cancellationToken);

        var zipPaths = Directory.GetFiles(RootPath, "*.zip")
            .OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
            .ToList();

        var errors = new List<string>();
        var importedCount = 0;
        var complaintIds = new List<int>();

        foreach (var zipPath in zipPaths)
        {
            try
            {
                var zipBytes = await File.ReadAllBytesAsync(zipPath, cancellationToken);
                var complaintId = await ProcessZipAsync(zipBytes, Path.GetFileName(zipPath), null, cancellationToken);
                File.Delete(zipPath);
                importedCount++;
                complaintIds.Add(complaintId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error procesando el ZIP local {ZipPath}", zipPath);
                errors.Add($"{Path.GetFileName(zipPath)}: {ex.Message}");
            }
        }

        return new ImportSummary(zipPaths.Count, importedCount, errors, complaintIds);
    }

    public async Task<ImportSummary> ImportFromGlobalLeaksAsync(
        FileDownloadResult zipDownload,
        FileDownloadResult? jsonDownload,
        CancellationToken cancellationToken = default)
    {
        await EnsureStorageReadyAsync(cancellationToken);

        var fileName = string.IsNullOrWhiteSpace(zipDownload.FileName)
            ? $"report-{Guid.NewGuid():N}.zip"
            : zipDownload.FileName;

        var json = jsonDownload is null
            ? null
            : Encoding.UTF8.GetString(jsonDownload.Content);

        try
        {
            var complaintId = await ProcessZipAsync(zipDownload.Content, fileName, json, cancellationToken);
            return new ImportSummary(1, 1, [], [complaintId]);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error importando denuncia desde GlobalLeaks {FileName}", fileName);
            return new ImportSummary(1, 0, [$"{fileName}: {ex.Message}"]);
        }
    }

    private async Task<int> ProcessZipAsync(
        byte[] zipBytes,
        string sourceName,
        string? globalLeaksJson,
        CancellationToken cancellationToken)
    {
        using var zipStream = new MemoryStream(zipBytes, writable: false);
        using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);

        var reportEntry = FindReportEntry(archive);

        if (reportEntry is null)
        {
            var entries = archive.Entries
                .Where(entry => !string.IsNullOrWhiteSpace(entry.Name))
                .Select(entry => NormalizeEntryPath(entry.FullName))
                .Take(30)
                .ToArray();

            throw new InvalidOperationException(
                entries.Length == 0
                    ? "El ZIP no contiene ficheros."
                    : $"El ZIP no contiene un report reconocible. Ficheros encontrados: {string.Join(", ", entries)}");
        }

        var reportIsPdf = IsPdfEntry(reportEntry);
        var reportText = reportIsPdf
            ? string.Empty
            : await ReadEntryTextAsync(reportEntry, cancellationToken);
        var denuncia = reportIsPdf
            ? new DenunciasGestiona()
            : ReportParser.ParseReport(reportText);

        if (!string.IsNullOrWhiteSpace(globalLeaksJson))
        {
            GlobalLeaksJsonEnricher.Enrich(denuncia, globalLeaksJson);
        }
        else if (reportIsPdf)
        {
            throw new InvalidOperationException(
                "El report viene en PDF y no se ha recibido el JSON de GlobalLeaks necesario para extraer los datos de la denuncia.");
        }

        if (denuncia.Id_Denuncia == 0)
        {
            denuncia.Id_Denuncia = TryReadIdFromReport(reportText);
        }

        if (denuncia.Id_Denuncia == 0)
        {
            throw new InvalidOperationException(
                $"No se ha podido determinar el identificador de la denuncia en {sourceName}.");
        }

        if (reportIsPdf)
        {
            reportText = BuildSyntheticReportText(denuncia);
            denuncia.TextoOriginalReport = reportText;
        }

        denuncia.ProcedureId = Guid.Empty;
        denuncia.GroupId = Guid.Empty;
        if (string.IsNullOrWhiteSpace(denuncia.Expediente_Gestiona))
        {
            denuncia.Expediente_Gestiona = "Pendiente";
        }

        var nuevosFicheros = await ReadFilesFromArchiveAsync(archive, reportEntry, denuncia.Id_Denuncia, cancellationToken);
        await MergeComplaintAsync(denuncia, cancellationToken);
        await MergeFilesAsync(nuevosFicheros, cancellationToken);
        return denuncia.Id_Denuncia;
    }

    private async Task<List<FicherosDenuncias>> ReadFilesFromArchiveAsync(
        ZipArchive archive,
        ZipArchiveEntry reportEntry,
        int denunciaId,
        CancellationToken cancellationToken)
    {
        var files = new List<FicherosDenuncias>
        {
            new(
                id_Fichero: 0,
                id_Tipo: 1,
                descripcion: IsPdfEntry(reportEntry) ? "report.pdf original" : "report.txt original",
                fecha: reportEntry.LastWriteTime.UtcDateTime == DateTime.MinValue
                    ? DateTime.UtcNow
                    : reportEntry.LastWriteTime.UtcDateTime,
                observaciones: "",
                id_Denuncia: denunciaId,
                nombreFichero: IsPdfEntry(reportEntry) ? "report.pdf" : "report.txt",
                fichero: await ReadEntryBytesAsync(reportEntry, cancellationToken))
        };

        foreach (var entry in archive.Entries.Where(entry => IsSupportedAttachmentEntry(entry) && !IsSameEntry(entry, reportEntry)))
        {
            files.Add(new FicherosDenuncias(
                id_Fichero: 0,
                id_Tipo: 1,
                descripcion: null,
                fecha: entry.LastWriteTime.UtcDateTime == DateTime.MinValue
                    ? DateTime.UtcNow
                    : entry.LastWriteTime.UtcDateTime,
                observaciones: "",
                id_Denuncia: denunciaId,
                nombreFichero: Path.GetFileName(entry.FullName),
                fichero: await ReadEntryBytesAsync(entry, cancellationToken)));
        }

        return files;
    }

    private async Task MergeComplaintAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken)
    {
        var existing = await _denunciaStore.GetDenunciaByIdAsync(denuncia.Id_Denuncia, cancellationToken);

        if (existing is not null)
        {
            CopyComplaintData(existing, denuncia);
            await CompleteGestionaStatusAsync(existing, existing, cancellationToken);
            await _denunciaStore.UpsertDenunciaAsync(existing, cancellationToken);
            return;
        }

        await CompleteGestionaStatusAsync(denuncia, null, cancellationToken);
        await _denunciaStore.UpsertDenunciaAsync(denuncia, cancellationToken);
    }

    private Task MergeFilesAsync(List<FicherosDenuncias> nuevosFicheros, CancellationToken cancellationToken)
    {
        return _denunciaStore.UpsertFicherosAsync(nuevosFicheros, cancellationToken);
    }

    private async Task CompleteGestionaStatusAsync(
        DenunciasGestiona denuncia,
        CancellationToken cancellationToken)
    {
        await CompleteGestionaStatusAsync(denuncia, null, cancellationToken);
    }

    private async Task CompleteGestionaStatusAsync(
        DenunciasGestiona denuncia,
        DenunciasGestiona? storedComplaint,
        CancellationToken cancellationToken)
    {
        if (TryApplyGestionaStatusFromStore(denuncia, storedComplaint))
        {
            return;
        }

        try
        {
            var match = await _gestionaService.BuscarExpedientePorIdEnAsuntoAsync(denuncia.Id_Denuncia);
            if (match is null)
            {
                ApplyPendingGestionaStatus(denuncia);
                return;
            }

            denuncia.EsActualizacion = true;
            denuncia.EnGestiona = true;
            denuncia.Expediente_Gestiona = match.FileUrl;
            denuncia.CodigoExpedienteGestiona = match.CodigoExpediente ?? string.Empty;
            denuncia.NombreDenuncia = match.FreeTitle ?? $"Denuncia {denuncia.Id_Denuncia}-CD";
        }
        catch (Exception ex)
        {
            ApplyPendingGestionaStatus(denuncia);
            _logger.LogWarning(
                ex,
                "No se ha podido comprobar si la denuncia {DenunciaId} ya existia en Gestiona",
                denuncia.Id_Denuncia);
        }
    }

    private static bool TryApplyGestionaStatusFromStore(
        DenunciasGestiona target,
        DenunciasGestiona? storedComplaint)
    {
        if (!IsAlreadyUploadedToGestiona(storedComplaint))
        {
            return false;
        }

        target.EsActualizacion = true;
        target.EnGestiona = true;
        target.Expediente_Gestiona = string.IsNullOrWhiteSpace(storedComplaint!.Expediente_Gestiona)
            ? "Pendiente"
            : storedComplaint.Expediente_Gestiona;
        target.CodigoExpedienteGestiona = storedComplaint.CodigoExpedienteGestiona ?? string.Empty;

        if (!string.IsNullOrWhiteSpace(storedComplaint.NombreDenuncia))
        {
            target.NombreDenuncia = storedComplaint.NombreDenuncia;
        }

        if (storedComplaint.FechaSubidaAGestiona != DateTime.MinValue)
        {
            target.FechaSubidaAGestiona = storedComplaint.FechaSubidaAGestiona;
        }

        return !string.IsNullOrWhiteSpace(target.CodigoExpedienteGestiona);
    }

    private static void ApplyPendingGestionaStatus(DenunciasGestiona denuncia)
    {
        denuncia.EsActualizacion = false;
        denuncia.EnGestiona = false;
        denuncia.Expediente_Gestiona = "Pendiente";
        denuncia.CodigoExpedienteGestiona = string.Empty;
        if (string.IsNullOrWhiteSpace(denuncia.NombreDenuncia))
        {
            denuncia.NombreDenuncia = $"Denuncia {denuncia.Id_Denuncia}-CD";
        }
    }

    private static bool IsAlreadyUploadedToGestiona(DenunciasGestiona? denuncia)
    {
        if (denuncia is null)
        {
            return false;
        }

        if (denuncia.EnGestiona || denuncia.FechaSubidaAGestiona != DateTime.MinValue)
        {
            return true;
        }

        return !string.IsNullOrWhiteSpace(denuncia.Expediente_Gestiona) &&
               !string.Equals(denuncia.Expediente_Gestiona, "Pendiente", StringComparison.OrdinalIgnoreCase);
    }

    private static void CopyComplaintData(DenunciasGestiona target, DenunciasGestiona source)
    {
        target.Fecha = source.Fecha;
        target.Etiqueta = source.Etiqueta;
        target.Estado = source.Estado;
        target.Tipo_Denuncia = source.Tipo_Denuncia;
        target.TipoDenunciante = source.TipoDenunciante;
        target.EsPersonaJuridica = source.EsPersonaJuridica;
        target.Confidencial = source.Confidencial;
        target.Nombre = source.Nombre;
        target.PrimerApellido = source.PrimerApellido;
        target.SegundoApellido = source.SegundoApellido;
        target.Apellidos = source.Apellidos;
        target.RazonSocial = source.RazonSocial;
        target.Sexo = source.Sexo;
        target.Dni = source.Dni;
        target.TipoDocumentoIdentificativo = source.TipoDocumentoIdentificativo;
        target.PaisOrigen = source.PaisOrigen;
        target.Asunto = source.Asunto;
        target.A_Quien_Denuncia = source.A_Quien_Denuncia;
        target.DenunciadoDetalle = source.DenunciadoDetalle;
        target.Descripcion_Denuncia = source.Descripcion_Denuncia;
        target.Denunciado_Ante_Inst = source.Denunciado_Ante_Inst;
        target.OrganismoDenunciado = source.OrganismoDenunciado;
        target.SolicitaProteccion = source.SolicitaProteccion;
        target.MedidasProteccionSolicitadas = source.MedidasProteccionSolicitadas;
        target.Modalidad_Informacion = source.Modalidad_Informacion;
        target.Lugar_Hechos = source.Lugar_Hechos;
        target.Fecha_Hechos = source.Fecha_Hechos;
        target.AutorizaRemision = source.AutorizaRemision;
        target.PreferenciaRemision = source.PreferenciaRemision;
        target.Notificacion_Preferencia = source.Notificacion_Preferencia;
        target.Notificacion_Electronica = source.Notificacion_Electronica;
        target.SeguimientoOnline = source.SeguimientoOnline;
        target.NotificacionPostal = source.NotificacionPostal;
        target.Correo_Electronico = source.Correo_Electronico;
        target.Notificacion_Sms = source.Notificacion_Sms;
        target.Condiciones = source.Condiciones;
        target.Comments = source.Comments;
        target.Telefono = source.Telefono;
        target.Direccion = source.Direccion;
        target.DireccionTipoVia = source.DireccionTipoVia;
        target.DireccionNumero = source.DireccionNumero;
        target.DireccionPiso = source.DireccionPiso;
        target.DireccionPuerta = source.DireccionPuerta;
        target.DireccionBloque = source.DireccionBloque;
        target.DireccionEscalera = source.DireccionEscalera;
        target.DireccionExtra = source.DireccionExtra;
        target.Municipio = source.Municipio;
        target.Provincia = source.Provincia;
        target.CodigoPostal = source.CodigoPostal;
        target.Pais = source.Pais;
        target.CamposFormularioJson = source.CamposFormularioJson;
        target.TextoOriginalReport = source.TextoOriginalReport;
    }

    private static bool IsSupportedAttachmentEntry(ZipArchiveEntry entry)
    {
        if (string.IsNullOrWhiteSpace(entry.Name))
        {
            return false;
        }

        var normalized = NormalizeEntryPath(entry.FullName);
        return IsDirectChildOf(normalized, "files") || IsDirectChildOf(normalized, "files_attached_from_recipients");
    }

    private static ZipArchiveEntry? FindReportEntry(ZipArchive archive)
    {
        return archive.Entries.FirstOrDefault(IsReportEntry);
    }

    private static bool IsReportEntry(ZipArchiveEntry entry)
    {
        if (string.IsNullOrWhiteSpace(entry.Name))
        {
            return false;
        }

        var fileName = Path.GetFileName(NormalizeEntryPath(entry.FullName));
        if (string.Equals(fileName, "report.txt", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
        var extension = Path.GetExtension(fileName);
        return (extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
                extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase)) &&
               nameWithoutExtension.StartsWith("report", StringComparison.OrdinalIgnoreCase);
    }

    private static bool IsPdfEntry(ZipArchiveEntry entry)
    {
        return Path.GetExtension(entry.Name).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
    }

    private static bool IsSameEntry(ZipArchiveEntry left, ZipArchiveEntry right)
    {
        return string.Equals(
            NormalizeEntryPath(left.FullName),
            NormalizeEntryPath(right.FullName),
            StringComparison.OrdinalIgnoreCase);
    }

    private static bool IsDirectChildOf(string normalizedEntryPath, string rootFolder)
    {
        if (!normalizedEntryPath.StartsWith(rootFolder + "/", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        var remaining = normalizedEntryPath[(rootFolder.Length + 1)..];
        return !remaining.Contains('/');
    }

    private static string NormalizeEntryPath(string path)
    {
        return path.Replace('\\', '/').Trim('/');
    }

    private static async Task<string> ReadEntryTextAsync(
        ZipArchiveEntry entry,
        CancellationToken cancellationToken)
    {
        var bytes = await ReadEntryBytesAsync(entry, cancellationToken);
        return Encoding.UTF8.GetString(bytes);
    }

    private static async Task<byte[]> ReadEntryBytesAsync(
        ZipArchiveEntry entry,
        CancellationToken cancellationToken)
    {
        await using var entryStream = entry.Open();
        using var memory = new MemoryStream();
        await entryStream.CopyToAsync(memory, cancellationToken);
        return memory.ToArray();
    }

    private static int TryReadIdFromReport(string reportText)
    {
        using var reader = new StringReader(reportText);
        string? line;

        while ((line = reader.ReadLine()) is not null)
        {
            line = line.Trim();
            if (line.StartsWith("ID:", StringComparison.OrdinalIgnoreCase) &&
                int.TryParse(line[3..].Trim(), out var id))
            {
                return id;
            }
        }

        return 0;
    }

    private static string BuildSyntheticReportText(DenunciasGestiona denuncia)
    {
        var builder = new StringBuilder();
        builder.AppendLine($"ID: {denuncia.Id_Denuncia}");
        if (denuncia.Fecha != DateTime.MinValue)
        {
            builder.AppendLine($"Fecha: {denuncia.Fecha:O}");
        }

        AppendMetadata(builder, "Etiqueta", denuncia.Etiqueta);
        AppendMetadata(builder, "Estado", denuncia.Estado);
        builder.AppendLine();

        AppendSection(builder, "Datos del denunciante",
            ("Indique si actúa como persona física o en representación de una persona jurídica.", denuncia.TipoDenunciante),
            ("Nombre", denuncia.Nombre),
            ("1º Apellido", denuncia.PrimerApellido),
            ("2º Apellido", denuncia.SegundoApellido),
            ("Razón social", denuncia.RazonSocial),
            ("SEXO", denuncia.Sexo),
            ("CONTACTO TELEFÓNICO", denuncia.Telefono),
            ("País de Origen", denuncia.PaisOrigen),
            ("NIF (DNI, NIE)", denuncia.Dni));

        AppendSection(builder, "Descripción",
            ("Asunto", denuncia.Asunto),
            ("¿A quién denuncia?", denuncia.A_Quien_Denuncia),
            ("Describa su denuncia", denuncia.Descripcion_Denuncia),
            ("¿Ha denunciado estos hechos ante otras instituciones u órganos?", denuncia.Denunciado_Ante_Inst),
            ("POR FAVOR. INDIQUE EL ORGANISMO O LA INSTITUCION DONDE HA DENUNCIADO LOS HECHOS", denuncia.OrganismoDenunciado),
            ("¿Solicita medidas concretas de protección?", denuncia.SolicitaProteccion),
            ("DESCRIBA LAS MEDIDAS DE PROTECCIÓN SOLICITADAS", denuncia.MedidasProteccionSolicitadas),
            ("Lugar en el que ocurrieron los hechos que denuncia", denuncia.Lugar_Hechos),
            ("Fecha de los hechos que denuncia", denuncia.Fecha_Hechos == DateTime.MinValue ? string.Empty : denuncia.Fecha_Hechos.ToString("dd/MM/yyyy")),
            ("Autorización para remitir su denuncia", denuncia.AutorizaRemision),
            ("En tal caso, ¿desea que su denuncia se remita anonimizada (sin datos personales)?", denuncia.PreferenciaRemision));

        AppendSection(builder, "Preferencias de notificación",
            ("Preferencia de notificación", denuncia.Notificacion_Preferencia),
            ("Notificaciones Electrónicas", denuncia.Notificacion_Electronica),
            ("Correo electrónico", denuncia.Correo_Electronico),
            ("Seguimiento Online", denuncia.SeguimientoOnline),
            ("Autorizo recibir notificaciones vía Correo Postal", denuncia.NotificacionPostal),
            ("Provincia", denuncia.Provincia),
            ("Tipo de vía", denuncia.DireccionTipoVia),
            ("Nombre de la vía", denuncia.Direccion),
            ("Código Postal", denuncia.CodigoPostal),
            ("Localidad", denuncia.Municipio),
            ("Número/Km", denuncia.DireccionNumero),
            ("Bloque", denuncia.DireccionBloque),
            ("Escalera", denuncia.DireccionEscalera),
            ("Planta", denuncia.DireccionPiso),
            ("Puerta", denuncia.DireccionPuerta),
            ("Extra", denuncia.DireccionExtra));

        if (!string.IsNullOrWhiteSpace(denuncia.Comments))
        {
            builder.AppendLine("{Messages}");
            builder.AppendLine(denuncia.Comments);
        }

        return builder.ToString();
    }

    private static void AppendMetadata(StringBuilder builder, string label, string? value)
    {
        if (!string.IsNullOrWhiteSpace(value))
        {
            builder.AppendLine($"{label}: {value}");
        }
    }

    private static void AppendSection(StringBuilder builder, string section, params (string Label, string? Value)[] fields)
    {
        builder.AppendLine(section);
        foreach (var (label, value) in fields)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                continue;
            }

            builder.AppendLine($"  {label}");
            builder.AppendLine($"    {value}");
        }

        builder.AppendLine();
    }
}

