using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Services.Contables
{
    public class PagoDistribuidorService : IPagoDistribuidorService
    {
        private readonly IPagoDistribuidorRepository _pagoRepo;
        private readonly IDistribuidorRepository _distribuidorRepo;
        private readonly ITipoPagoRepository _tipoPagoRepo;
        private readonly IEmpresaRepository _empresaRepo;
        private readonly ISaldoRepository _saldoRepo;
        private readonly IPeriodoCerradoValidator _periodoCerrado;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<PagoDistribuidorService> _logger;

        public PagoDistribuidorService(
            IPagoDistribuidorRepository pagoRepo,
            IDistribuidorRepository distribuidorRepo,
            ITipoPagoRepository tipoPagoRepo,
            IEmpresaRepository empresaRepo,
            ISaldoRepository saldoRepo,
            IPeriodoCerradoValidator periodoCerrado,
            DbConnectionFactory connectionFactory,
            ILogger<PagoDistribuidorService> logger)
        {
            _pagoRepo = pagoRepo;
            _distribuidorRepo = distribuidorRepo;
            _tipoPagoRepo = tipoPagoRepo;
            _empresaRepo = empresaRepo;
            _saldoRepo = saldoRepo;
            _periodoCerrado = periodoCerrado;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private async Task<PagoDistribuidorDto> MapToDto(PagoDistribuidor pago)
        {
            if (pago == null) return null!;

            var distribuidorData = await _distribuidorRepo.GetByIdAsync(pago.IdDistribuidor);
            var tipoPago = await _tipoPagoRepo.GetByIdAsync(pago.IdTipoPago);
            var empresa = await _empresaRepo.GetByIdAsync(pago.IdEmpresa);

            return new PagoDistribuidorDto
            {
                IdPago = pago.IdPago,
                IdDistribuidor = pago.IdDistribuidor,
                NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A",
                Fecha = pago.Fecha.ToString("yyyy-MM-dd"),
                TipoMovimiento = pago.TipoMovimiento,
                Recibo = pago.Recibo,
                Monto = pago.Monto,
                IdTipoPago = pago.IdTipoPago,
                NombreTipoPago = tipoPago?.Nombre ?? "N/A",
                Detalle = pago.Detalle,
                IdEmpresa = pago.IdEmpresa,
                NombreEmpresa = empresa?.Nombre ?? "N/A"
            };
        }

        public async Task<IEnumerable<PagoDistribuidorDto>> ObtenerTodosAsync(
            DateTime? fechaDesde, DateTime? fechaHasta,
            int? idDistribuidor, int? idEmpresa, string? tipoMovimiento)
        {
            var pagos = await _pagoRepo.GetAllAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento);
            var dtos = new List<PagoDistribuidorDto>();
            foreach (var pago in pagos)
            {
                dtos.Add(await MapToDto(pago));
            }
            return dtos;
        }

        public async Task<PagoDistribuidorDto?> ObtenerPorIdAsync(int idPago)
        {
            var pago = await _pagoRepo.GetByIdAsync(idPago);
            return pago == null ? null : await MapToDto(pago);
        }

        public async Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario)
        {
            if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDistribuidor) == null)
                return (null, "Distribuidor no válido.");
            if (await _tipoPagoRepo.GetByIdAsync(createDto.IdTipoPago) == null)
                return (null, "Tipo de pago no válido.");
            if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
                return (null, "Empresa no válida.");
            var pagoExistente = await _pagoRepo.GetByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento);
            if (pagoExistente != null)
            {
                // Si encontramos un duplicado, obtenemos los detalles para el mensaje de error
                var distribuidor = await _distribuidorRepo.GetByIdSimpleAsync(pagoExistente.IdDistribuidor);
                var empresa = await _empresaRepo.GetByIdAsync(pagoExistente.IdEmpresa);

                string mensajeError = $"El recibo N° {createDto.Recibo} ya fue registrado como '{pagoExistente.TipoMovimiento}' el {pagoExistente.Fecha:dd/MM/yyyy} " +
                                      $"para el distribuidor '{distribuidor?.Nombre ?? "Desconocido"}' en la empresa '{empresa?.Nombre ?? "Desconocida"}'.";

                return (null, mensajeError);
            }

            // Bloqueo por período cerrado: la fecha de operación no puede caer dentro de un cierre vigente.
            var bloqueoCrear = await _periodoCerrado.EstaCerradoAsync("Distribuidores", createDto.IdDistribuidor, createDto.IdEmpresa, createDto.Fecha);
            if (bloqueoCrear.EstaCerrado)
                throw new BloqueoPorPeriodoCerradoException(bloqueoCrear.IdCierre!.Value, bloqueoCrear.FechaCorte!.Value);

            var nuevoPago = new PagoDistribuidor
            {
                IdDistribuidor = createDto.IdDistribuidor,
                Fecha = createDto.Fecha.Date,
                TipoMovimiento = createDto.TipoMovimiento,
                Recibo = createDto.Recibo,
                Monto = createDto.Monto,
                IdTipoPago = createDto.IdTipoPago,
                Detalle = createDto.Detalle,
                IdEmpresa = createDto.IdEmpresa
            };

            using var connection = _connectionFactory.CreateConnection();
            IDbTransaction? transaction = null; // Declarar fuera para el finally
            try
            {
                if (connection.State != ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
                }
                transaction = connection.BeginTransaction();

                var pagoCreado = await _pagoRepo.CreateAsync(nuevoPago, idUsuario, transaction);
                if (pagoCreado == null) throw new DataException("Error al registrar el pago.");

                decimal montoParaSaldo;
                if (createDto.TipoMovimiento == "Recibido")
                {
                    montoParaSaldo = -createDto.Monto;
                }
                else
                {
                    montoParaSaldo = createDto.Monto;
                }

                bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoParaSaldo, transaction);
                if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");

                transaction.Commit();
                _logger.LogInformation("PagoDistribuidor ID {Id} creado y saldo afectado por Usuario ID {UserId}.", pagoCreado.IdPago, idUsuario);
                return (await MapToDto(pagoCreado), null);
            }
            catch (Exception ex)
            {
                try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de CrearAsync PagoDistribuidor."); }
                _logger.LogError(ex, "Error CrearAsync PagoDistribuidor.");
                return (null, $"Error interno: {ex.Message}");
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
                }
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            IDbTransaction? transaction = null;
            try
            {
                if (connection.State != ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
                }
                transaction = connection.BeginTransaction();

                var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
                if (pagoExistente == null)
                {
                    transaction.Rollback(); // Rollback si no se encuentra
                    return (false, "Pago no encontrado.");
                }

                // Bloqueo por período cerrado sobre la fecha original del pago (el DTO de update no permite cambiar Fecha).
                var bloqueoUpd = await _periodoCerrado.EstaCerradoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, pagoExistente.Fecha);
                if (bloqueoUpd.EstaCerrado)
                {
                    transaction.Rollback();
                    throw new BloqueoPorPeriodoCerradoException(bloqueoUpd.IdCierre!.Value, bloqueoUpd.FechaCorte!.Value);
                }

                if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null)
                {
                    transaction.Rollback();
                    return (false, "Tipo de pago no válido.");
                }

                decimal impactoOriginalSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto;
                decimal impactoNuevoSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -updateDto.Monto : updateDto.Monto;
                decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo;

                var pagoParaActualizarEnRepo = new PagoDistribuidor
                {
                    IdPago = pagoExistente.IdPago,
                    IdDistribuidor = pagoExistente.IdDistribuidor,
                    Fecha = pagoExistente.Fecha,
                    TipoMovimiento = pagoExistente.TipoMovimiento,
                    Recibo = pagoExistente.Recibo,
                    Monto = updateDto.Monto,
                    IdTipoPago = updateDto.IdTipoPago,
                    Detalle = updateDto.Detalle,
                    IdEmpresa = pagoExistente.IdEmpresa
                };

                var actualizado = await _pagoRepo.UpdateAsync(pagoParaActualizarEnRepo, idUsuario, transaction);
                if (!actualizado) throw new DataException("Error al actualizar el pago en la base de datos.");

                if (diferenciaAjusteSaldo != 0)
                {
                    bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, diferenciaAjusteSaldo, transaction);
                    if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización del pago.");
                }

                transaction.Commit();
                _logger.LogInformation("PagoDistribuidor ID {Id} actualizado por Usuario ID {UserId}.", idPago, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
            catch (BloqueoPorPeriodoCerradoException)
            {
                try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (Bloqueo)."); }
                throw;
            }
            catch (Exception ex)
            {
                try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor."); }
                _logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago);
                return (false, $"Error interno: {ex.Message}");
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
                }
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            IDbTransaction? transaction = null;
            try
            {
                if (connection.State != ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
                }
                transaction = connection.BeginTransaction();

                var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
                if (pagoExistente == null)
                {
                    transaction.Rollback();
                    return (false, "Pago no encontrado.");
                }

                // Bloqueo por período cerrado: no se puede eliminar un pago cuya fecha cae en un cierre vigente.
                var bloqueoDel = await _periodoCerrado.EstaCerradoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, pagoExistente.Fecha);
                if (bloqueoDel.EstaCerrado)
                {
                    transaction.Rollback();
                    throw new BloqueoPorPeriodoCerradoException(bloqueoDel.IdCierre!.Value, bloqueoDel.FechaCorte!.Value);
                }

                decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto;

                var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar el pago de la base de datos.");

                bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, montoReversion, transaction);
                if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación del pago.");

                transaction.Commit();
                _logger.LogInformation("PagoDistribuidor ID {Id} eliminado por Usuario ID {UserId}.", idPago, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
            catch (BloqueoPorPeriodoCerradoException)
            {
                try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (Bloqueo)."); }
                throw;
            }
            catch (Exception ex)
            {
                try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor."); }
                _logger.LogError(ex, "Error EliminarAsync PagoDistribuidor ID: {Id}", idPago);
                return (false, $"Error interno: {ex.Message}");
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
                }
            }
        }

        public async Task<IEnumerable<PagoDistribuidorHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPagoAfectado)
        {
            var historialData = await _pagoRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado);

            return historialData.Select(h => new PagoDistribuidorHistorialDto
            {
                Id_Pago = h.Historial.Id_Pago,
                Id_Distribuidor = h.Historial.Id_Distribuidor,
                Fecha = h.Historial.Fecha,
                TipoMovimiento = h.Historial.TipoMovimiento,
                Recibo = h.Historial.Recibo,
                Monto = h.Historial.Monto,
                Id_TipoPago = h.Historial.Id_TipoPago,
                Detalle = h.Historial.Detalle,
                Id_Empresa = h.Historial.Id_Empresa,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}