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

namespace GestionIntegral.Api.Services.Suscripciones
{
    public class SuscriptorService : ISuscriptorService
    {
        private readonly ISuscriptorRepository _suscriptorRepository;
        private readonly IFormaPagoRepository _formaPagoRepository;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<SuscriptorService> _logger;

        public SuscriptorService(
            ISuscriptorRepository suscriptorRepository,
            IFormaPagoRepository formaPagoRepository,
            DbConnectionFactory connectionFactory,
            ILogger<SuscriptorService> logger)
        {
            _suscriptorRepository = suscriptorRepository;
            _formaPagoRepository = formaPagoRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        // Helper para mapear Modelo -> DTO, enriqueciendo con el nombre de la forma de pago
        private async Task<SuscriptorDto?> MapToDto(Suscriptor suscriptor)
        {
            if (suscriptor == null) return null;

            var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida);

            return new SuscriptorDto
            {
                IdSuscriptor = suscriptor.IdSuscriptor,
                NombreCompleto = suscriptor.NombreCompleto,
                Email = suscriptor.Email,
                Telefono = suscriptor.Telefono,
                Direccion = suscriptor.Direccion,
                TipoDocumento = suscriptor.TipoDocumento,
                NroDocumento = suscriptor.NroDocumento,
                CBU = suscriptor.CBU,
                IdFormaPagoPreferida = suscriptor.IdFormaPagoPreferida,
                NombreFormaPagoPreferida = formaPago?.Nombre ?? "Desconocida",
                Observaciones = suscriptor.Observaciones,
                Activo = suscriptor.Activo
            };
        }

        public async Task<IEnumerable<SuscriptorDto>> ObtenerTodos(string? nombreFilter, string? nroDocFilter, bool soloActivos)
        {
            // 1. Obtener todos los suscriptores en una sola consulta
            var suscriptores = await _suscriptorRepository.GetAllAsync(nombreFilter, nroDocFilter, soloActivos);
            if (!suscriptores.Any())
            {
                return Enumerable.Empty<SuscriptorDto>();
            }

            // 2. Obtener todas las formas de pago en una sola consulta
            // y convertirlas a un diccionario para una búsqueda rápida (O(1) en lugar de O(n)).
            var formasDePago = (await _formaPagoRepository.GetAllAsync())
                                .ToDictionary(fp => fp.IdFormaPago);

            // 3. Mapear en memoria, evitando múltiples llamadas a la base de datos.
            var dtos = suscriptores.Select(s =>
            {
                // Busca la forma de pago en el diccionario en memoria.
                formasDePago.TryGetValue(s.IdFormaPagoPreferida, out var formaPago);

                return new SuscriptorDto
                {
                    IdSuscriptor = s.IdSuscriptor,
                    NombreCompleto = s.NombreCompleto,
                    Email = s.Email,
                    Telefono = s.Telefono,
                    Direccion = s.Direccion,
                    TipoDocumento = s.TipoDocumento,
                    NroDocumento = s.NroDocumento,
                    CBU = s.CBU,
                    IdFormaPagoPreferida = s.IdFormaPagoPreferida,
                    NombreFormaPagoPreferida = formaPago?.Nombre ?? "Desconocida", // Asigna el nombre
                    Observaciones = s.Observaciones,
                    Activo = s.Activo
                };
            });

            return dtos;
        }

        public async Task<SuscriptorDto?> ObtenerPorId(int id)
        {
            var suscriptor = await _suscriptorRepository.GetByIdAsync(id);
            if (suscriptor == null)
                return null;
            return await MapToDto(suscriptor);
        }

        public async Task<(SuscriptorDto? Suscriptor, string? Error)> Crear(CreateSuscriptorDto createDto, int idUsuario)
        {
            // Validación de Lógica de Negocio
            if (await _suscriptorRepository.ExistsByDocumentoAsync(createDto.TipoDocumento, createDto.NroDocumento))
            {
                return (null, "Ya existe un suscriptor con el mismo tipo y número de documento.");
            }

            var formaPago = await _formaPagoRepository.GetByIdAsync(createDto.IdFormaPagoPreferida);
            if (formaPago == null || !formaPago.Activo)
            {
                return (null, "La forma de pago seleccionada no es válida o está inactiva.");
            }
            if (formaPago.RequiereCBU && string.IsNullOrWhiteSpace(createDto.CBU))
            {
                return (null, "El CBU es obligatorio para la forma de pago seleccionada.");
            }

            var nuevoSuscriptor = new Suscriptor
            {
                NombreCompleto = createDto.NombreCompleto,
                Email = createDto.Email,
                Telefono = createDto.Telefono,
                Direccion = createDto.Direccion,
                TipoDocumento = createDto.TipoDocumento,
                NroDocumento = createDto.NroDocumento,
                CBU = createDto.CBU,
                IdFormaPagoPreferida = createDto.IdFormaPagoPreferida,
                Observaciones = createDto.Observaciones,
                IdUsuarioAlta = idUsuario
            };

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

            try
            {
                var suscriptorCreado = await _suscriptorRepository.CreateAsync(nuevoSuscriptor, transaction);
                if (suscriptorCreado == null) throw new DataException("La creación en el repositorio devolvió null.");

                transaction.Commit();
                _logger.LogInformation("Suscriptor ID {IdSuscriptor} creado por Usuario ID {IdUsuario}.", suscriptorCreado.IdSuscriptor, idUsuario);

                var dtoCreado = await MapToDto(suscriptorCreado);
                return (dtoCreado, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error al crear suscriptor: {Nombre}", createDto.NombreCompleto);
                return (null, $"Error interno al crear el suscriptor: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> Actualizar(int id, UpdateSuscriptorDto updateDto, int idUsuario)
        {
            var suscriptorExistente = await _suscriptorRepository.GetByIdAsync(id);
            if (suscriptorExistente == null) return (false, "Suscriptor no encontrado.");

            if (await _suscriptorRepository.ExistsByDocumentoAsync(updateDto.TipoDocumento, updateDto.NroDocumento, id))
            {
                return (false, "El tipo y número de documento ya pertenecen a otro suscriptor.");
            }

            var formaPago = await _formaPagoRepository.GetByIdAsync(updateDto.IdFormaPagoPreferida);
            if (formaPago == null || !formaPago.Activo)
            {
                return (false, "La forma de pago seleccionada no es válida o está inactiva.");
            }
            if (formaPago.RequiereCBU && string.IsNullOrWhiteSpace(updateDto.CBU))
            {
                return (false, "El CBU es obligatorio para la forma de pago seleccionada.");
            }

            // Mapeo DTO -> Modelo
            suscriptorExistente.NombreCompleto = updateDto.NombreCompleto;
            suscriptorExistente.Email = updateDto.Email;
            suscriptorExistente.Telefono = updateDto.Telefono;
            suscriptorExistente.Direccion = updateDto.Direccion;
            suscriptorExistente.TipoDocumento = updateDto.TipoDocumento;
            suscriptorExistente.NroDocumento = updateDto.NroDocumento;
            suscriptorExistente.CBU = updateDto.CBU;
            suscriptorExistente.IdFormaPagoPreferida = updateDto.IdFormaPagoPreferida;
            suscriptorExistente.Observaciones = updateDto.Observaciones;
            suscriptorExistente.IdUsuarioMod = idUsuario;
            suscriptorExistente.FechaMod = DateTime.Now;

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

            try
            {
                var actualizado = await _suscriptorRepository.UpdateAsync(suscriptorExistente, transaction);
                if (!actualizado) throw new DataException("La actualización en el repositorio devolvió false.");

                transaction.Commit();
                _logger.LogInformation("Suscriptor ID {IdSuscriptor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
                return (true, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error al actualizar suscriptor ID: {IdSuscriptor}", id);
                return (false, $"Error interno al actualizar: {ex.Message}");
            }
        }

        private async Task<(bool Exito, string? Error)> CambiarEstadoActivo(int id, bool activar, int idUsuario)
        {
            var suscriptor = await _suscriptorRepository.GetByIdAsync(id);
            if (suscriptor == null) return (false, "Suscriptor no encontrado.");

            if (!activar && await _suscriptorRepository.IsInUseAsync(id))
            {
                return (false, "No se puede desactivar un suscriptor con suscripciones activas.");
            }

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

            try
            {
                var actualizado = await _suscriptorRepository.ToggleActivoAsync(id, activar, idUsuario, transaction);
                if (!actualizado) throw new DataException("No se pudo cambiar el estado del suscriptor.");

                transaction.Commit();
                _logger.LogInformation("El estado del Suscriptor ID {IdSuscriptor} se cambió a {Estado} por el Usuario ID {IdUsuario}.", id, activar ? "Activo" : "Inactivo", idUsuario);
                return (true, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error al cambiar estado del suscriptor ID: {IdSuscriptor}", id);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public Task<(bool Exito, string? Error)> Desactivar(int id, int idUsuario)
        {
            return CambiarEstadoActivo(id, false, idUsuario);
        }

        public Task<(bool Exito, string? Error)> Activar(int id, int idUsuario)
        {
            return CambiarEstadoActivo(id, true, idUsuario);
        }
    }
}