using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Suscripciones;
using GestionIntegral.Api.Models.Suscripciones;
using System.Data;
using System.Text;
using GestionIntegral.Api.Dtos.Suscripciones;

namespace GestionIntegral.Api.Services.Suscripciones
{
    public class DebitoAutomaticoService : IDebitoAutomaticoService
    {
        private readonly IFacturaRepository _facturaRepository;
        private readonly ISuscriptorRepository _suscriptorRepository;
        private readonly ILoteDebitoRepository _loteDebitoRepository;
        private readonly IFormaPagoRepository _formaPagoRepository;
        private readonly IPagoRepository _pagoRepository;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<DebitoAutomaticoService> _logger;

        private const string NRO_PRESTACION = "26435"; // Reemplazar por el número real
        private const string ORIGEN_EMPRESA = "EMPRESA";

        public DebitoAutomaticoService(
            IFacturaRepository facturaRepository,
            ISuscriptorRepository suscriptorRepository,
            ILoteDebitoRepository loteDebitoRepository,
            IFormaPagoRepository formaPagoRepository,
            IPagoRepository pagoRepository,
            DbConnectionFactory connectionFactory,
            ILogger<DebitoAutomaticoService> logger)
        {
            _facturaRepository = facturaRepository;
            _suscriptorRepository = suscriptorRepository;
            _loteDebitoRepository = loteDebitoRepository;
            _formaPagoRepository = formaPagoRepository;
            _pagoRepository = pagoRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        public async Task<(string? ContenidoArchivo, string? NombreArchivo, string? Error)> GenerarArchivoPagoDirecto(int anio, int mes, int idUsuario)
        {
            // Este número debe ser gestionado para no repetirse. Por ahora, lo mantenemos como 1.
            const int identificacionArchivo = 1;

            var periodo = $"{anio}-{mes:D2}";
            var fechaGeneracion = DateTime.Now;

            using var connection = _connectionFactory.CreateConnection();
            await (connection as System.Data.Common.DbConnection)!.OpenAsync();
            using var transaction = connection.BeginTransaction();

            try
            {
                var facturasParaDebito = await GetFacturasParaDebito(periodo, transaction);
                if (!facturasParaDebito.Any())
                {
                    return (null, null, "No se encontraron facturas pendientes de cobro por débito automático para el período seleccionado.");
                }

                var importeTotal = facturasParaDebito.Sum(f => f.Factura.ImporteFinal);
                var cantidadRegistros = facturasParaDebito.Count();
                var nombreArchivo = $"{NRO_PRESTACION}{fechaGeneracion:yyyyMMdd}{identificacionArchivo}.txt";

                var nuevoLote = new LoteDebito
                {
                    Periodo = periodo,
                    NombreArchivo = nombreArchivo,
                    ImporteTotal = importeTotal,
                    CantidadRegistros = cantidadRegistros,
                    IdUsuarioGeneracion = idUsuario
                };
                var loteCreado = await _loteDebitoRepository.CreateAsync(nuevoLote, transaction);
                if (loteCreado == null) throw new DataException("No se pudo crear el registro del lote de débito.");

                var sb = new StringBuilder();
                sb.Append(CrearRegistroHeader(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo));
                foreach (var item in facturasParaDebito)
                {
                    sb.Append(CrearRegistroDetalle(item.Factura, item.Suscriptor));
                }
                sb.Append(CrearRegistroTrailer(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo));

                var idsFacturas = facturasParaDebito.Select(f => f.Factura.IdFactura);
                bool actualizadas = await _facturaRepository.UpdateLoteDebitoAsync(idsFacturas, loteCreado.IdLoteDebito, transaction);
                if (!actualizadas) throw new DataException("No se pudieron actualizar las facturas con la información del lote.");

                transaction.Commit();
                _logger.LogInformation("Archivo de débito {NombreArchivo} generado exitosamente para el período {Periodo}.", nombreArchivo, periodo);
                return (sb.ToString(), nombreArchivo, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error crítico al generar el archivo de débito para el período {Periodo}", periodo);
                return (null, null, $"Error interno: {ex.Message}");
            }
        }

        private async Task<List<(Factura Factura, Suscriptor Suscriptor)>> GetFacturasParaDebito(string periodo, IDbTransaction transaction)
        {
            var facturas = await _facturaRepository.GetByPeriodoAsync(periodo);
            var resultado = new List<(Factura, Suscriptor)>();

            // Filtramos por estado Y POR TIPO DE FACTURA
            foreach (var f in facturas.Where(fa =>
                (fa.EstadoPago == "Pendiente" || fa.EstadoPago == "Pagada Parcialmente" || fa.EstadoPago == "Rechazada") &&
                fa.TipoFactura == "Mensual"
            ))
            {
                var suscriptor = await _suscriptorRepository.GetByIdAsync(f.IdSuscriptor);
                if (suscriptor == null || string.IsNullOrWhiteSpace(suscriptor.CBU) || suscriptor.CBU.Length != 22)
                {
                    _logger.LogWarning("Suscriptor ID {IdSuscriptor} omitido del lote de débito por CBU inválido o ausente (se esperan 22 dígitos).", f.IdSuscriptor);
                    continue;
                }
                var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida);
                if (formaPago != null && formaPago.RequiereCBU)
                {
                    resultado.Add((f, suscriptor));
                }
            }
            return resultado;
        }

        private string ConvertirCbuBanelcoASnp(string cbu22)
        {
            if (string.IsNullOrEmpty(cbu22) || cbu22.Length != 22) return "".PadRight(26);
            try
            {
                string bloque1 = cbu22.Substring(0, 8);
                string bloque2 = cbu22.Substring(8);
                return $"0{bloque1}000{bloque2}";
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al parsear y convertir CBU de 22 dígitos: {CBU}", cbu22);
                return "".PadRight(26);
            }
        }

        // --- Helpers de Formateo ---
        private string FormatString(string? value, int length) => (value ?? "").PadRight(length);
        private string FormatNumeric(long value, int length) => value.ToString().PadLeft(length, '0');
        private string FormatNumericString(string? value, int length) => (value ?? "").PadLeft(length, '0');
        private string MapTipoDocumento(string tipo) => tipo.ToUpper() switch
        {
            "DNI" => "0096",
            "CUIT" => "0080",
            "CUIL" => "0086",
            "LE" => "0089",
            "LC" => "0090",
            _ => "0000"
        };

        private string CrearRegistroHeader(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
        {
            var sb = new StringBuilder();
            sb.Append("00");
            sb.Append(FormatNumericString(NRO_PRESTACION, 6));
            sb.Append("C");
            sb.Append(fechaGeneracion.ToString("yyyyMMdd"));
            sb.Append(FormatString(identificacionArchivo.ToString(), 1));
            sb.Append(FormatString(ORIGEN_EMPRESA, 7));
            sb.Append(FormatNumeric((long)(importeTotal * 100), 14));
            sb.Append(FormatNumeric(cantidadRegistros, 7));
            sb.Append(FormatString("", 304));
            sb.Append("\r\n");
            return sb.ToString();
        }

        private string CrearRegistroDetalle(Factura factura, Suscriptor suscriptor)
        {
            string cbu26 = ConvertirCbuBanelcoASnp(suscriptor.CBU!);
            var sb = new StringBuilder();
            sb.Append("0370");
            sb.Append(FormatString(suscriptor.IdSuscriptor.ToString(), 22));
            sb.Append(cbu26);
            sb.Append(FormatString($"SUSC-{factura.IdFactura}", 15));
            sb.Append(factura.FechaVencimiento.ToString("yyyyMMdd"));
            sb.Append(FormatNumeric((long)(factura.ImporteFinal * 100), 14));
            sb.Append(FormatNumeric(0, 8)); // Fecha 2do Vencimiento
            sb.Append(FormatNumeric(0, 14)); // Importe 2do Vencimiento
            sb.Append(FormatNumeric(0, 8)); // Fecha 3er Vencimiento
            sb.Append(FormatNumeric(0, 14)); // Importe 3er Vencimiento
            sb.Append("0");
            sb.Append(FormatString("", 3));
            sb.Append(FormatString(MapTipoDocumento(suscriptor.TipoDocumento), 4));
            sb.Append(FormatNumericString(suscriptor.NroDocumento, 11));
            sb.Append(FormatString("", 22));
            sb.Append(FormatString("", 26));
            sb.Append(FormatNumeric(0, 14));
            sb.Append(FormatNumeric(0, 8));
            sb.Append(FormatString("", 22));
            sb.Append(FormatString("", 40));
            sb.Append(FormatString($"Susc.{factura.Periodo}", 10));
            sb.Append(FormatNumeric(0, 8));
            sb.Append(FormatNumeric(0, 14));
            sb.Append(FormatNumeric(0, 8));
            sb.Append(FormatString("", 26));
            sb.Append("\r\n");
            return sb.ToString();
        }

        private string CrearRegistroTrailer(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
        {
            var sb = new StringBuilder();
            sb.Append("99");
            sb.Append(FormatNumericString(NRO_PRESTACION, 6));
            sb.Append("C");
            sb.Append(fechaGeneracion.ToString("yyyyMMdd"));
            sb.Append(FormatString(identificacionArchivo.ToString(), 1));
            sb.Append(FormatString(ORIGEN_EMPRESA, 7));
            sb.Append(FormatNumeric((long)(importeTotal * 100), 14));
            sb.Append(FormatNumeric(cantidadRegistros, 7));
            sb.Append(FormatString("", 304));
            sb.Append("\r\n");
            return sb.ToString();
        }

        public async Task<ProcesamientoLoteResponseDto> ProcesarArchivoRespuesta(IFormFile archivo, int idUsuario)
        {
            // Se mantiene la lógica original para procesar el archivo de respuesta del banco.

            var respuesta = new ProcesamientoLoteResponseDto();
            if (archivo == null || archivo.Length == 0)
            {
                respuesta.Errores.Add("No se proporcionó ningún archivo o el archivo está vacío.");
                return respuesta;
            }

            using var connection = _connectionFactory.CreateConnection();
            await (connection as System.Data.Common.DbConnection)!.OpenAsync();
            using var transaction = connection.BeginTransaction();

            try
            {
                using var reader = new StreamReader(archivo.OpenReadStream());
                string? linea;
                while ((linea = await reader.ReadLineAsync()) != null)
                {
                    if (linea.Length < 20) continue;
                    respuesta.TotalRegistrosLeidos++;

                    var referencia = linea.Substring(0, 15).Trim();
                    var estadoProceso = linea.Substring(15, 2).Trim();
                    var motivoRechazo = linea.Substring(17, 3).Trim();
                    if (!int.TryParse(referencia.Replace("SUSC-", ""), out int idFactura))
                    {
                        respuesta.Errores.Add($"Línea #{respuesta.TotalRegistrosLeidos}: No se pudo extraer un ID de factura válido de la referencia '{referencia}'.");
                        continue;
                    }

                    var factura = await _facturaRepository.GetByIdAsync(idFactura);
                    if (factura == null)
                    {
                        respuesta.Errores.Add($"Línea #{respuesta.TotalRegistrosLeidos}: La factura con ID {idFactura} no fue encontrada en el sistema.");
                        continue;
                    }

                    var nuevoPago = new Pago
                    {
                        IdFactura = idFactura,
                        FechaPago = DateTime.Now.Date,
                        IdFormaPago = 1, // Se asume una forma de pago para el débito.
                        Monto = factura.ImporteFinal,
                        IdUsuarioRegistro = idUsuario,
                        Referencia = $"Lote {factura.IdLoteDebito} - Banco"
                    };

                    if (estadoProceso == "AP")
                    {
                        nuevoPago.Estado = "Aprobado";
                        await _pagoRepository.CreateAsync(nuevoPago, transaction);
                        await _facturaRepository.UpdateEstadoPagoAsync(idFactura, "Pagada", transaction);
                        respuesta.PagosAprobados++;
                    }
                    else
                    {
                        nuevoPago.Estado = "Rechazado";
                        await _pagoRepository.CreateAsync(nuevoPago, transaction);
                        await _facturaRepository.UpdateEstadoYMotivoAsync(idFactura, "Rechazada", motivoRechazo, transaction);
                        respuesta.PagosRechazados++;
                    }
                }

                transaction.Commit();
                respuesta.MensajeResumen = $"Archivo procesado. Leídos: {respuesta.TotalRegistrosLeidos}, Aprobados: {respuesta.PagosAprobados}, Rechazados: {respuesta.PagosRechazados}.";
                _logger.LogInformation(respuesta.MensajeResumen);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error crítico al procesar archivo de respuesta de débito.");
                respuesta.Errores.Add($"Error fatal en el procesamiento: {ex.Message}");
            }

            return respuesta;
        }
    }
}