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

namespace GestionIntegral.Api.Services.Distribucion
{
    public class PorcPagoService : IPorcPagoService
    {
        private readonly IPorcPagoRepository _porcPagoRepository;
        private readonly IPublicacionRepository _publicacionRepository;
        private readonly IDistribuidorRepository _distribuidorRepository;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<PorcPagoService> _logger;

        public PorcPagoService(
            IPorcPagoRepository porcPagoRepository,
            IPublicacionRepository publicacionRepository,
            IDistribuidorRepository distribuidorRepository,
            DbConnectionFactory connectionFactory,
            ILogger<PorcPagoService> logger)
        {
            _porcPagoRepository = porcPagoRepository;
            _publicacionRepository = publicacionRepository;
            _distribuidorRepository = distribuidorRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private PorcPagoDto MapToDto((PorcPago PorcPago, string NombreDistribuidor) data) => new PorcPagoDto
        {
            IdPorcentaje = data.PorcPago.IdPorcentaje,
            IdPublicacion = data.PorcPago.IdPublicacion,
            IdDistribuidor = data.PorcPago.IdDistribuidor,
            NombreDistribuidor = data.NombreDistribuidor,
            VigenciaD = data.PorcPago.VigenciaD.ToString("yyyy-MM-dd"),
            VigenciaH = data.PorcPago.VigenciaH?.ToString("yyyy-MM-dd"),
            Porcentaje = data.PorcPago.Porcentaje
        };

        private async Task<PorcPagoDto> MapToDtoWithLookup(PorcPago porcPago)
        {
            var distribuidorData = await _distribuidorRepository.GetByIdAsync(porcPago.IdDistribuidor);
            return new PorcPagoDto
            {
                IdPorcentaje = porcPago.IdPorcentaje,
                IdPublicacion = porcPago.IdPublicacion,
                IdDistribuidor = porcPago.IdDistribuidor,
                NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "Distribuidor Desconocido",
                VigenciaD = porcPago.VigenciaD.ToString("yyyy-MM-dd"),
                VigenciaH = porcPago.VigenciaH?.ToString("yyyy-MM-dd"),
                Porcentaje = porcPago.Porcentaje
            };
        }

        public async Task<IEnumerable<PorcPagoDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
        {
            var data = await _porcPagoRepository.GetByPublicacionIdAsync(idPublicacion);
            return data.Select(MapToDto);
        }

        public async Task<PorcPagoDto?> ObtenerPorIdAsync(int idPorcentaje)
        {
            var porcPago = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
            if (porcPago == null) return null;
            return await MapToDtoWithLookup(porcPago);
        }

        public async Task<(PorcPagoDto? PorcPago, string? Error)> CrearAsync(CreatePorcPagoDto createDto, int idUsuario)
        {
            if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
                return (null, "La publicación especificada no existe.");
            var distribuidorData = await _distribuidorRepository.GetByIdAsync(createDto.IdDistribuidor);
            if (distribuidorData.Distribuidor == null)
                return (null, "El distribuidor especificado no existe.");

            var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.VigenciaD.Date);
            if (porcPagoActivo != null)
            {
                return (null, $"Ya existe un porcentaje de pago activo para esta publicación y distribuidor en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior primero.");
            }

            var nuevoPorcPago = new PorcPago
            {
                IdPublicacion = createDto.IdPublicacion,
                IdDistribuidor = createDto.IdDistribuidor,
                VigenciaD = createDto.VigenciaD.Date,
                VigenciaH = null,
                Porcentaje = createDto.Porcentaje
            };

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var porcPagoAnterior = await _porcPagoRepository.GetPreviousActivePorcPagoAsync(createDto.IdPublicacion, createDto.IdDistribuidor, nuevoPorcPago.VigenciaD, transaction);
                if (porcPagoAnterior != null)
                {
                    if (porcPagoAnterior.VigenciaD.Date >= nuevoPorcPago.VigenciaD.Date)
                    {
                        transaction.Rollback();
                        return (null, $"La fecha de inicio del nuevo porcentaje ({nuevoPorcPago.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último porcentaje vigente ({porcPagoAnterior.VigenciaD:dd/MM/yyyy}) para este distribuidor.");
                    }
                    porcPagoAnterior.VigenciaH = nuevoPorcPago.VigenciaD.AddDays(-1);
                    await _porcPagoRepository.UpdateAsync(porcPagoAnterior, idUsuario, transaction);
                }

                var porcPagoCreado = await _porcPagoRepository.CreateAsync(nuevoPorcPago, idUsuario, transaction);
                if (porcPagoCreado == null) throw new DataException("Error al crear el porcentaje de pago.");

                transaction.Commit();
                _logger.LogInformation("PorcPago ID {Id} creado por Usuario ID {UserId}.", porcPagoCreado.IdPorcentaje, idUsuario);
                return (new PorcPagoDto
                { // Construir DTO manualmente ya que tenemos NombreDistribuidor
                    IdPorcentaje = porcPagoCreado.IdPorcentaje,
                    IdPublicacion = porcPagoCreado.IdPublicacion,
                    IdDistribuidor = porcPagoCreado.IdDistribuidor,
                    NombreDistribuidor = distribuidorData.Distribuidor.Nombre,
                    VigenciaD = porcPagoCreado.VigenciaD.ToString("yyyy-MM-dd"),
                    VigenciaH = porcPagoCreado.VigenciaH?.ToString("yyyy-MM-dd"),
                    Porcentaje = porcPagoCreado.Porcentaje
                }, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error CrearAsync PorcPago para Pub ID {IdPub}, Dist ID {IdDist}", createDto.IdPublicacion, createDto.IdDistribuidor);
                return (null, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcentaje, UpdatePorcPagoDto updateDto, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var porcPagoExistente = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
                if (porcPagoExistente == null) return (false, "Porcentaje de pago no encontrado.");

                if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < porcPagoExistente.VigenciaD.Date)
                    return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");

                if (updateDto.VigenciaH.HasValue)
                {
                    var porcentajesPubDist = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoExistente.IdPublicacion);
                    var porcentajesPosteriores = porcentajesPubDist
                        .Where(p => p.PorcPago.IdDistribuidor == porcPagoExistente.IdDistribuidor &&
                                    p.PorcPago.IdPorcentaje != idPorcentaje &&
                                    p.PorcPago.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
                                    p.PorcPago.VigenciaD.Date > porcPagoExistente.VigenciaD.Date);
                    if (porcentajesPosteriores.Any())
                    {
                        return (false, "No se puede cerrar este período porque existen porcentajes posteriores para este distribuidor que se solaparían.");
                    }
                }

                porcPagoExistente.Porcentaje = updateDto.Porcentaje;
                if (updateDto.VigenciaH.HasValue)
                {
                    porcPagoExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
                }

                var actualizado = await _porcPagoRepository.UpdateAsync(porcPagoExistente, idUsuario, transaction);
                if (!actualizado) throw new DataException("Error al actualizar porcentaje de pago.");

                transaction.Commit();
                _logger.LogInformation("PorcPago ID {Id} actualizado por Usuario ID {UserId}.", idPorcentaje, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error ActualizarAsync PorcPago ID: {Id}", idPorcentaje);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcentaje, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var porcPagoAEliminar = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
                if (porcPagoAEliminar == null) return (false, "Porcentaje de pago no encontrado.");

                if (porcPagoAEliminar.VigenciaH == null)
                {
                    var todosPorcPubDistData = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoAEliminar.IdPublicacion);
                    var todosPorcPubDist = todosPorcPubDistData
                                                .Where(p => p.PorcPago.IdDistribuidor == porcPagoAEliminar.IdDistribuidor)
                                                .Select(p => p.PorcPago)
                                                .OrderByDescending(p => p.VigenciaD).ToList();

                    var indiceActual = todosPorcPubDist.FindIndex(p => p.IdPorcentaje == idPorcentaje);
                    if (indiceActual != -1 && (indiceActual + 1) < todosPorcPubDist.Count)
                    {
                        var porcPagoAnteriorDirecto = todosPorcPubDist[indiceActual + 1];
                        if (porcPagoAnteriorDirecto.VigenciaH.HasValue &&
                           porcPagoAnteriorDirecto.VigenciaH.Value.Date == porcPagoAEliminar.VigenciaD.AddDays(-1).Date)
                        {
                            porcPagoAnteriorDirecto.VigenciaH = null;
                            await _porcPagoRepository.UpdateAsync(porcPagoAnteriorDirecto, idUsuario, transaction);
                        }
                    }
                }

                var eliminado = await _porcPagoRepository.DeleteAsync(idPorcentaje, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar porcentaje de pago.");

                transaction.Commit();
                _logger.LogInformation("PorcPago ID {Id} eliminado por Usuario ID {UserId}.", idPorcentaje, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error EliminarAsync PorcPago ID: {Id}", idPorcentaje);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<IEnumerable<PorcPagoHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPorcentajeAfectado, int? idPublicacionAfectada, int? idDistribuidorAfectado)
        {
            var historialData = await _porcPagoRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPorcentajeAfectado, idPublicacionAfectada, idDistribuidorAfectado);

            return historialData.Select(h => new PorcPagoHistorialDto
            {
                Id_Porcentaje = h.Historial.Id_Porcentaje,
                Id_Publicacion = h.Historial.Id_Publicacion,
                Id_Distribuidor = h.Historial.Id_Distribuidor,
                VigenciaD = h.Historial.VigenciaD,
                VigenciaH = h.Historial.VigenciaH,
                Porcentaje = h.Historial.Porcentaje,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}