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 RecargoZonaService : IRecargoZonaService
    {
        private readonly IRecargoZonaRepository _recargoZonaRepository;
        private readonly IPublicacionRepository _publicacionRepository;
        private readonly IZonaRepository _zonaRepository;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<RecargoZonaService> _logger;

        public RecargoZonaService(
            IRecargoZonaRepository recargoZonaRepository,
            IPublicacionRepository publicacionRepository,
            IZonaRepository zonaRepository,
            DbConnectionFactory connectionFactory,
            ILogger<RecargoZonaService> logger)
        {
            _recargoZonaRepository = recargoZonaRepository;
            _publicacionRepository = publicacionRepository;
            _zonaRepository = zonaRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private RecargoZonaDto MapToDto((RecargoZona Recargo, string NombreZona) data) => new RecargoZonaDto
        {
            IdRecargo = data.Recargo.IdRecargo,
            IdPublicacion = data.Recargo.IdPublicacion,
            IdZona = data.Recargo.IdZona,
            NombreZona = data.NombreZona,
            VigenciaD = data.Recargo.VigenciaD.ToString("yyyy-MM-dd"),
            VigenciaH = data.Recargo.VigenciaH?.ToString("yyyy-MM-dd"),
            Valor = data.Recargo.Valor
        };

        // Helper para mapear cuando solo tenemos el RecargoZona y necesitamos buscar NombreZona
        private async Task<RecargoZonaDto> MapToDtoWithZonaLookup(RecargoZona recargo)
        {
            var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // zonaRepository.GetByIdAsync devuelve ZonaDto
            return new RecargoZonaDto
            {
                IdRecargo = recargo.IdRecargo,
                IdPublicacion = recargo.IdPublicacion,
                IdZona = recargo.IdZona,
                NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva", // Usar la propiedad Nombre del ZonaDto
                VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
                VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
                Valor = recargo.Valor
            };
        }


        public async Task<IEnumerable<RecargoZonaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
        {
            var recargosData = await _recargoZonaRepository.GetByPublicacionIdAsync(idPublicacion);
            return recargosData
                .Select(rd => MapToDto(rd)) // MapToDto ahora devuelve RecargoZonaDto?
                .Where(dto => dto != null)
                .Select(dto => dto!); // Cast no nulo
        }

        public async Task<RecargoZonaDto?> ObtenerPorIdAsync(int idRecargo)
        {
            var recargo = await _recargoZonaRepository.GetByIdAsync(idRecargo);
            if (recargo == null) return null;

            var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // Obtiene ZonaDto
            return new RecargoZonaDto
            {
                IdRecargo = recargo.IdRecargo,
                IdPublicacion = recargo.IdPublicacion,
                IdZona = recargo.IdZona,
                NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva",
                VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
                VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
                Valor = recargo.Valor
            };
        }

        public async Task<(RecargoZonaDto? Recargo, string? Error)> CrearAsync(CreateRecargoZonaDto createDto, int idUsuario)
        {
            if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
                return (null, "La publicación especificada no existe.");
            var zona = await _zonaRepository.GetByIdAsync(createDto.IdZona); // Devuelve ZonaDto
            if (zona == null)
                return (null, "La zona especificada no existe o no está activa.");

            // Usar createDto.VigenciaD directamente que ya es DateTime
            var recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, createDto.IdZona, createDto.VigenciaD.Date);
            if (recargoActivo != null)
            {
                return (null, $"Ya existe un recargo activo para esta publicación y zona en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior.");
            }

            var nuevoRecargo = new RecargoZona
            {
                IdPublicacion = createDto.IdPublicacion,
                IdZona = createDto.IdZona,
                VigenciaD = createDto.VigenciaD.Date,
                VigenciaH = null,
                Valor = createDto.Valor
            };

            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 recargoAnterior = await _recargoZonaRepository.GetPreviousActiveRecargoAsync(createDto.IdPublicacion, createDto.IdZona, nuevoRecargo.VigenciaD, transaction);
                if (recargoAnterior != null)
                {
                    if (recargoAnterior.VigenciaD.Date >= nuevoRecargo.VigenciaD.Date) // Comparar solo fechas
                    {
                        transaction.Rollback();
                        return (null, $"La fecha de inicio del nuevo recargo ({nuevoRecargo.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último recargo vigente ({recargoAnterior.VigenciaD:dd/MM/yyyy}) para esta zona.");
                    }
                    recargoAnterior.VigenciaH = nuevoRecargo.VigenciaD.AddDays(-1);
                    await _recargoZonaRepository.UpdateAsync(recargoAnterior, idUsuario, transaction);
                }

                var recargoCreado = await _recargoZonaRepository.CreateAsync(nuevoRecargo, idUsuario, transaction);
                if (recargoCreado == null) throw new DataException("Error al crear el recargo.");

                transaction.Commit();
                _logger.LogInformation("Recargo ID {Id} creado por Usuario ID {UserId}.", recargoCreado.IdRecargo, idUsuario);
                // Pasar el nombre de la zona ya obtenido
                return (new RecargoZonaDto
                {
                    IdRecargo = recargoCreado.IdRecargo,
                    IdPublicacion = recargoCreado.IdPublicacion,
                    IdZona = recargoCreado.IdZona,
                    NombreZona = zona.Nombre,
                    VigenciaD = recargoCreado.VigenciaD.ToString("yyyy-MM-dd"),
                    VigenciaH = recargoCreado.VigenciaH?.ToString("yyyy-MM-dd"),
                    Valor = recargoCreado.Valor
                }, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error CrearAsync RecargoZona para Pub ID {IdPub}, Zona ID {IdZona}", createDto.IdPublicacion, createDto.IdZona);
                return (null, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int idRecargo, UpdateRecargoZonaDto 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 recargoExistente = await _recargoZonaRepository.GetByIdAsync(idRecargo);
                if (recargoExistente == null) return (false, "Recargo por zona no encontrado.");

                // --- VERIFICACIÓN PERÍODO CERRADO ---
                if (recargoExistente.VigenciaH != null && updateDto.Valor != recargoExistente.Valor)
                {
                    transaction.Rollback();
                    return (false, "No se puede modificar el valor de un recargo que ya ha sido cerrado (tiene Vigencia Hasta).");
                }

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

                if (updateDto.VigenciaH.HasValue)
                {
                    var recargosDeLaPublicacion = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoExistente.IdPublicacion);
                    var recargosPosteriores = recargosDeLaPublicacion
                                            .Where(r => r.Recargo.IdZona == recargoExistente.IdZona &&
                                                        r.Recargo.IdRecargo != idRecargo &&
                                                        r.Recargo.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
                                                        r.Recargo.VigenciaD.Date > recargoExistente.VigenciaD.Date);
                    if (recargosPosteriores.Any())
                    {
                        return (false, "No se puede cerrar este período porque existen recargos posteriores para esta zona que se solaparían.");
                    }
                }

                recargoExistente.Valor = updateDto.Valor;
                if (updateDto.VigenciaH.HasValue)
                {
                    recargoExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
                }

                var actualizado = await _recargoZonaRepository.UpdateAsync(recargoExistente, idUsuario, transaction);
                if (!actualizado) throw new DataException("Error al actualizar recargo.");

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

        public async Task<(bool Exito, string? Error)> EliminarAsync(int idRecargo, 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 recargoAEliminar = await _recargoZonaRepository.GetByIdAsync(idRecargo);
                if (recargoAEliminar == null) return (false, "Recargo no encontrado.");

                // --- VERIFICACIÓN DE USO ---
                if (await _recargoZonaRepository.IsInUseAsync(idRecargo, recargoAEliminar.VigenciaD, recargoAEliminar.VigenciaH))
                {
                    transaction.Rollback();
                    return (false, "No se puede eliminar. Este recargo está referenciado en movimientos existentes.");
                }

                if (recargoAEliminar.VigenciaH == null)
                {
                    var todosRecargosPubZonaData = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoAEliminar.IdPublicacion);
                    var todosRecargosPubZona = todosRecargosPubZonaData
                                                .Where(r => r.Recargo.IdZona == recargoAEliminar.IdZona)
                                                .Select(r => r.Recargo)
                                                .OrderByDescending(r => r.VigenciaD).ToList();

                    var indiceActual = todosRecargosPubZona.FindIndex(r => r.IdRecargo == idRecargo);
                    if (indiceActual != -1 && (indiceActual + 1) < todosRecargosPubZona.Count)
                    {
                        var recargoAnteriorDirecto = todosRecargosPubZona[indiceActual + 1];
                        if (recargoAnteriorDirecto.VigenciaH.HasValue && recargoAnteriorDirecto.VigenciaH.Value.Date == recargoAEliminar.VigenciaD.AddDays(-1).Date)
                        {
                            recargoAnteriorDirecto.VigenciaH = null;
                            await _recargoZonaRepository.UpdateAsync(recargoAnteriorDirecto, idUsuario, transaction);
                            _logger.LogInformation("Recargo anterior ID {IdRecargoAnterior} reabierto tras eliminación de Recargo ID {IdRecargoEliminado}.", recargoAnteriorDirecto.IdRecargo, idRecargo);
                        }
                    }
                }

                var eliminado = await _recargoZonaRepository.DeleteAsync(idRecargo, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar recargo.");

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

        public async Task<IEnumerable<RecargoZonaHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idRecargoAfectado, int? idPublicacionAfectada, int? idZonaAfectada)
        {
            var historialData = await _recargoZonaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idRecargoAfectado, idPublicacionAfectada, idZonaAfectada);

            return historialData.Select(h => new RecargoZonaHistorialDto
            {
                Id_Recargo = h.Historial.Id_Recargo,
                Id_Publicacion = h.Historial.Id_Publicacion,
                Id_Zona = h.Historial.Id_Zona,
                VigenciaD = h.Historial.VigenciaD,
                VigenciaH = h.Historial.VigenciaH,
                Valor = h.Historial.Valor,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
                // Mapear NombrePublicacion y NombreZona si se añaden al DTO
            }).ToList();
        }
    }
}