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.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Services.Distribucion
{
    public class PrecioService : IPrecioService
    {
        private readonly IPrecioRepository _precioRepository;
        private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<PrecioService> _logger;

        public PrecioService(
            IPrecioRepository precioRepository,
            IPublicacionRepository publicacionRepository,
            DbConnectionFactory connectionFactory,
            ILogger<PrecioService> logger)
        {
            _precioRepository = precioRepository;
            _publicacionRepository = publicacionRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private PrecioDto MapToDto(Precio precio) => new PrecioDto
        {
            IdPrecio = precio.IdPrecio,
            IdPublicacion = precio.IdPublicacion,
            VigenciaD = precio.VigenciaD.ToString("yyyy-MM-dd"),
            VigenciaH = precio.VigenciaH?.ToString("yyyy-MM-dd"),
            Lunes = precio.Lunes,
            Martes = precio.Martes,
            Miercoles = precio.Miercoles,
            Jueves = precio.Jueves,
            Viernes = precio.Viernes,
            Sabado = precio.Sabado,
            Domingo = precio.Domingo
        };

        public async Task<IEnumerable<PrecioDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
        {
            var precios = await _precioRepository.GetByPublicacionIdAsync(idPublicacion);
            return precios.Select(MapToDto);
        }
        public async Task<PrecioDto?> ObtenerPorIdAsync(int idPrecio)
        {
            var precio = await _precioRepository.GetByIdAsync(idPrecio);
            return precio == null ? null : MapToDto(precio);
        }


        public async Task<(PrecioDto? Precio, string? Error)> CrearAsync(CreatePrecioDto createDto, int idUsuario)
        {
            if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
                return (null, "La publicación especificada no existe.");

            // Validar que no haya solapamiento de fechas o que VigenciaD sea lógica
            // Un precio no puede empezar antes que el VigenciaH de un precio anterior no cerrado.
            // Y no puede empezar en una fecha donde ya existe un precio activo para esa publicación.
            var precioActivoEnFecha = await _precioRepository.GetActiveByPublicacionAndDateAsync(createDto.IdPublicacion, createDto.VigenciaD);
            if (precioActivoEnFecha != null)
            {
                return (null, $"Ya existe un período de precios activo para esta publicación en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior.");
            }


            var nuevoPrecio = new Precio
            {
                IdPublicacion = createDto.IdPublicacion,
                VigenciaD = createDto.VigenciaD.Date, // Asegurar que solo sea fecha
                VigenciaH = null, // Se establece al crear el siguiente o al cerrar manualmente
                Lunes = createDto.Lunes ?? 0,
                Martes = createDto.Martes ?? 0,
                Miercoles = createDto.Miercoles ?? 0,
                Jueves = createDto.Jueves ?? 0,
                Viernes = createDto.Viernes ?? 0,
                Sabado = createDto.Sabado ?? 0,
                Domingo = createDto.Domingo ?? 0
            };

            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
            {
                // 1. Buscar el precio anterior activo para esta publicación que no tenga VigenciaH
                var precioAnterior = await _precioRepository.GetPreviousActivePriceAsync(createDto.IdPublicacion, nuevoPrecio.VigenciaD, transaction);

                if (precioAnterior != null)
                {
                    if (precioAnterior.VigenciaD >= nuevoPrecio.VigenciaD)
                    {
                        transaction.Rollback();
                        return (null, $"La fecha de inicio del nuevo precio ({nuevoPrecio.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último precio vigente ({precioAnterior.VigenciaD:dd/MM/yyyy}).");
                    }
                    // 2. Si existe, actualizar su VigenciaH al día anterior de la VigenciaD del nuevo precio
                    precioAnterior.VigenciaH = nuevoPrecio.VigenciaD.AddDays(-1);
                    bool actualizado = await _precioRepository.UpdateAsync(precioAnterior, idUsuario, transaction); // Usar un idUsuario de sistema/auditoría para esta acción automática si es necesario
                    if (!actualizado)
                    {
                        throw new DataException("No se pudo cerrar el período de precio anterior.");
                    }
                    _logger.LogInformation("Precio anterior ID {IdPrecioAnterior} cerrado con VigenciaH {VigenciaH}.", precioAnterior.IdPrecio, precioAnterior.VigenciaH);
                }

                // 3. Crear el nuevo registro de precio
                var precioCreado = await _precioRepository.CreateAsync(nuevoPrecio, idUsuario, transaction);
                if (precioCreado == null) throw new DataException("Error al crear el nuevo precio.");

                transaction.Commit();
                _logger.LogInformation("Precio ID {IdPrecio} creado para Publicación ID {IdPublicacion} por Usuario ID {IdUsuario}.", precioCreado.IdPrecio, precioCreado.IdPublicacion, idUsuario);
                return (MapToDto(precioCreado), null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error CrearAsync Precio para Publicación ID {IdPublicacion}", createDto.IdPublicacion);
                return (null, $"Error interno al crear el precio: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPrecio, UpdatePrecioDto 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 precioExistente = await _precioRepository.GetByIdAsync(idPrecio); // Obtener dentro de TX
                if (precioExistente == null)
                {
                    transaction.Rollback();
                    return (false, "Período de precio no encontrado.");
                }

                bool preciosDiasCambiaron =
                    (updateDto.Lunes.HasValue && updateDto.Lunes != precioExistente.Lunes) ||
                    (updateDto.Martes.HasValue && updateDto.Martes != precioExistente.Martes) ||
                    (updateDto.Miercoles.HasValue && updateDto.Miercoles != precioExistente.Miercoles) ||
                    (updateDto.Jueves.HasValue && updateDto.Jueves != precioExistente.Jueves) ||
                    (updateDto.Viernes.HasValue && updateDto.Viernes != precioExistente.Viernes) ||
                    (updateDto.Sabado.HasValue && updateDto.Sabado != precioExistente.Sabado) ||
                    (updateDto.Domingo.HasValue && updateDto.Domingo != precioExistente.Domingo);

                if (precioExistente.VigenciaH != null && preciosDiasCambiaron)
                {
                    transaction.Rollback();
                    return (false, "No se pueden modificar los precios de los días de un período que ya ha sido cerrado.");
                }

                // Validación 2: No modificar precios de días si el período está en uso
                if (preciosDiasCambiaron && await _precioRepository.IsInUseAsync(idPrecio, precioExistente.VigenciaD, precioExistente.VigenciaH))
                {
                    transaction.Rollback();
                    return (false, "No se pueden modificar los precios de los días de este período porque ya existen movimientos asociados.");
                }

                // Solo realizar validaciones de VigenciaH si se está intentando SETEAR o CAMBIAR VigenciaH
                if (updateDto.VigenciaH.HasValue) // <<--- CHEQUEO IMPORTANTE
                {
                    // Validación 3: Nueva VigenciaH debe ser >= VigenciaD
                    if (updateDto.VigenciaH.Value.Date < precioExistente.VigenciaD.Date)
                    {
                        transaction.Rollback();
                        return (false, "La Vigencia Hasta no puede ser anterior a la Vigencia Desde.");
                    }

                    // Validación 4: Nueva VigenciaH no debe solaparse con períodos posteriores
                    var todosLosPreciosPub = await _precioRepository.GetByPublicacionIdAsync(precioExistente.IdPublicacion);
                    if (todosLosPreciosPub.Any(p =>
                            p.IdPrecio != idPrecio &&
                            p.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
                            p.VigenciaD.Date > precioExistente.VigenciaD.Date &&
                            (p.VigenciaH == null || p.VigenciaH.Value.Date >= p.VigenciaD.Date)
                           ))
                    {
                        transaction.Rollback();
                        return (false, "No se puede cerrar este período con la Vigencia Hasta especificada porque se solaparía con un período posterior existente. Ajuste los períodos posteriores primero.");
                    }
                    // Si pasa las validaciones, se asigna la nueva VigenciaH más abajo
                }
                // Si updateDto.VigenciaH es null, estas validaciones de fecha no aplican.

                // Aplicar actualizaciones
                bool seRealizoAlgunaActualizacion = false;

                // Aplicar actualizaciones
                if (preciosDiasCambiaron)
                {
                    precioExistente.Lunes = updateDto.Lunes ?? precioExistente.Lunes;
                    precioExistente.Martes = updateDto.Martes ?? precioExistente.Martes;
                    precioExistente.Miercoles = updateDto.Miercoles ?? precioExistente.Miercoles;
                    precioExistente.Jueves = updateDto.Jueves ?? precioExistente.Jueves;
                    precioExistente.Viernes = updateDto.Viernes ?? precioExistente.Viernes;
                    precioExistente.Sabado = updateDto.Sabado ?? precioExistente.Sabado;
                    precioExistente.Domingo = updateDto.Domingo ?? precioExistente.Domingo;
                    seRealizoAlgunaActualizacion = true;
                }

                // Actualizar VigenciaH solo si el valor recibido en el DTO es diferente al existente,
                // o si se quiere pasar de un valor a null (reabrir) o de null a un valor (cerrar).
                if ((updateDto.VigenciaH.HasValue && (!precioExistente.VigenciaH.HasValue || updateDto.VigenciaH.Value.Date != precioExistente.VigenciaH.Value.Date)) ||
                    (!updateDto.VigenciaH.HasValue && precioExistente.VigenciaH.HasValue))
                {
                    precioExistente.VigenciaH = updateDto.VigenciaH.HasValue ? updateDto.VigenciaH.Value.Date : (DateTime?)null;
                    seRealizoAlgunaActualizacion = true;
                }

                if (seRealizoAlgunaActualizacion)
                {
                    var actualizado = await _precioRepository.UpdateAsync(precioExistente, idUsuario, transaction);
                    if (!actualizado) throw new DataException("Error al actualizar el período de precio.");
                }
                else
                {
                    _logger.LogInformation("No se detectaron cambios para Precio ID {IdPrecio}. No se realizó actualización.", idPrecio);
                }

                transaction.Commit();
                _logger.LogInformation("Precio ID {IdPrecio} procesado (actualizado si hubo cambios) por Usuario ID {IdUsuario}.", idPrecio, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch { } return (false, "Período de precio no encontrado."); }
            catch (Exception ex)
            {
                try { transaction?.Rollback(); } catch { }
                _logger.LogError(ex, "Error ActualizarAsync Precio ID: {IdPrecio}", idPrecio);
                return (false, $"Error interno al actualizar el período de precio: {ex.Message}");
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
                }
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int idPrecio, 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 precioAEliminar = await _precioRepository.GetByIdAsync(idPrecio); // Obtener dentro de TX
                if (precioAEliminar == null) return (false, "Período de precio no encontrado.");

                // --- VERIFICACIÓN DE USO ---
                if (await _precioRepository.IsInUseAsync(idPrecio, precioAEliminar.VigenciaD, precioAEliminar.VigenciaH))
                {
                    transaction.Rollback(); // No olvidar rollback si la verificación falla
                    return (false, "No se puede eliminar. Este período de precio está referenciado en movimientos existentes.");
                }
                if (precioAEliminar.VigenciaH == null)
                {
                    var todosLosPreciosPub = (await _precioRepository.GetByPublicacionIdAsync(precioAEliminar.IdPublicacion))
                                            .OrderByDescending(p => p.VigenciaD).ToList();
                    var indiceActual = todosLosPreciosPub.FindIndex(p => p.IdPrecio == idPrecio);
                    if (indiceActual != -1 && (indiceActual + 1) < todosLosPreciosPub.Count)
                    {
                        var precioAnteriorDirecto = todosLosPreciosPub[indiceActual + 1];
                        if (precioAnteriorDirecto.VigenciaH.HasValue && precioAnteriorDirecto.VigenciaH.Value.Date == precioAEliminar.VigenciaD.AddDays(-1).Date)
                        {
                            precioAnteriorDirecto.VigenciaH = null;
                            // Asegurar que UpdateAsync tome la transacción
                            await _precioRepository.UpdateAsync(precioAnteriorDirecto, idUsuario, transaction);
                            _logger.LogInformation("Precio anterior ID {IdPrecioAnterior} reabierto tras eliminación de Precio ID {IdPrecioEliminado}.", precioAnteriorDirecto.IdPrecio, idPrecio);
                        }
                    }
                }

                var eliminado = await _precioRepository.DeleteAsync(idPrecio, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar el período de precio.");

                transaction.Commit();
                _logger.LogInformation("Precio ID {IdPrecio} eliminado por Usuario ID {IdUsuario}.", idPrecio, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Período de precio no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error EliminarAsync Precio ID: {IdPrecio}", idPrecio);
                return (false, $"Error interno al eliminar el período de precio: {ex.Message}");
            }
        }

        public async Task<IEnumerable<PrecioHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPrecioAfectado, int? idPublicacionAfectada)
        {
            var historialData = await _precioRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPrecioAfectado, idPublicacionAfectada);

            return historialData.Select(h => new PrecioHistorialDto
            {
                Id_Precio = h.Historial.Id_Precio,
                Id_Publicacion = h.Historial.Id_Publicacion,
                VigenciaD = h.Historial.VigenciaD,
                VigenciaH = h.Historial.VigenciaH,
                Lunes = h.Historial.Lunes,
                Martes = h.Historial.Martes,
                Miercoles = h.Historial.Miercoles,
                Jueves = h.Historial.Jueves,
                Viernes = h.Historial.Viernes,
                Sabado = h.Historial.Sabado,
                Domingo = h.Historial.Domingo,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}