using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository
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 SaldoService : ISaldoService
    {
        private readonly ISaldoRepository _saldoRepo;
        private readonly IDistribuidorRepository _distribuidorRepo; // Para nombres
        private readonly ICanillaRepository _canillaRepo;         // Para nombres
        private readonly IEmpresaRepository _empresaRepo;           // Para nombres
        private readonly IPeriodoCerradoValidator _periodoCerrado;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<SaldoService> _logger;

        public SaldoService(
            ISaldoRepository saldoRepo,
            IDistribuidorRepository distribuidorRepo,
            ICanillaRepository canillaRepo,
            IEmpresaRepository empresaRepo,
            IPeriodoCerradoValidator periodoCerrado,
            DbConnectionFactory connectionFactory,
            ILogger<SaldoService> logger)
        {
            _saldoRepo = saldoRepo;
            _distribuidorRepo = distribuidorRepo;
            _canillaRepo = canillaRepo;
            _empresaRepo = empresaRepo;
            _periodoCerrado = periodoCerrado;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private async Task<SaldoGestionDto> MapToGestionDto(Saldo saldo)
        {
            if (saldo == null) return null!;

            string nombreDestinatario = "N/A";
            if (saldo.Destino == "Distribuidores")
            {
                var distData = await _distribuidorRepo.GetByIdAsync(saldo.IdDestino);
                nombreDestinatario = distData.Distribuidor?.Nombre ?? $"Dist. ID {saldo.IdDestino}";
            }
            else if (saldo.Destino == "Canillas")
            {
                var canData = await _canillaRepo.GetByIdAsync(saldo.IdDestino);
                nombreDestinatario = canData.Canilla?.NomApe ?? $"Can. ID {saldo.IdDestino}";
            }

            var empresa = await _empresaRepo.GetByIdAsync(saldo.IdEmpresa);

            return new SaldoGestionDto
            {
                IdSaldo = saldo.IdSaldo,
                Destino = saldo.Destino,
                IdDestino = saldo.IdDestino,
                NombreDestinatario = nombreDestinatario,
                IdEmpresa = saldo.IdEmpresa,
                NombreEmpresa = empresa?.Nombre ?? $"Emp. ID {saldo.IdEmpresa}",
                Monto = saldo.Monto,
                FechaUltimaModificacion = saldo.FechaUltimaModificacion
            };
        }

        public async Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
        {
            var saldos = await _saldoRepo.GetSaldosParaGestionAsync(destinoFilter, idDestinoFilter, idEmpresaFilter);
            var dtos = new List<SaldoGestionDto>();
            foreach (var saldo in saldos)
            {
                dtos.Add(await MapToGestionDto(saldo));
            }
            return dtos;
        }

        public async Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste)
        {
            if (ajusteDto.MontoAjuste == 0)
                return (false, "El monto de ajuste no puede ser cero.", null);

            // Validar existencia de Destino y Empresa
            if (ajusteDto.Destino == "Distribuidores")
            {
                if (await _distribuidorRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null)
                    return (false, "El distribuidor especificado no existe.", null);
            }
            else if (ajusteDto.Destino == "Canillas")
            {
                if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null)
                    return (false, "El canillita especificado no existe.", null);
            }
            else
            {
                return (false, "Tipo de destino inválido.", null);
            }
            if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null)
                return (false, "La empresa especificada no existe.", null);

            // Bloqueo por período cerrado: la FechaOperacion del ajuste no puede caer dentro de un cierre vigente.
            // El validator filtra internamente Destino="Distribuidores"; ajustes a Canillas no se bloquean.
            var bloqueo = await _periodoCerrado.EstaCerradoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, ajusteDto.FechaOperacion);
            if (bloqueo.EstaCerrado)
                throw new BloqueoPorPeriodoCerradoException(bloqueo.IdCierre!.Value, bloqueo.FechaCorte!.Value);

            using var connection = _connectionFactory.CreateConnection();
            if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); }
            using var transaction = connection.BeginTransaction();
            try
            {
                var saldoActual = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction);
                if (saldoActual == null)
                {
                    // Podríamos crear el saldo aquí si no existe y se quiere permitir un ajuste sobre un saldo nuevo.
                    // O devolver error. Por ahora, error.
                    transaction.Rollback();
                    return (false, "No se encontró un saldo existente para el destinatario y empresa especificados.", null);
                }

                decimal saldoAnterior = saldoActual.Monto;

                bool modificado = await _saldoRepo.ModificarSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, ajusteDto.MontoAjuste, transaction);
                if (!modificado)
                {
                    throw new DataException("No se pudo modificar el saldo principal.");
                }

                // Obtener el saldo después de la modificación para el historial
                var saldoDespuesDeModificacion = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction);
                if (saldoDespuesDeModificacion == null) throw new DataException("No se pudo obtener el saldo después de la modificación.");


                var historial = new SaldoAjusteHistorial
                {
                    Destino = ajusteDto.Destino,
                    IdDestino = ajusteDto.IdDestino,
                    IdEmpresa = ajusteDto.IdEmpresa,
                    MontoAjuste = ajusteDto.MontoAjuste,
                    SaldoAnterior = saldoAnterior,
                    SaldoNuevo = saldoDespuesDeModificacion.Monto, // saldoActual.Monto + ajusteDto.MontoAjuste,
                    Justificacion = ajusteDto.Justificacion,
                    FechaAjuste = DateTime.Now, // O UtcNow
                    IdUsuarioAjuste = idUsuarioAjuste
                };
                await _saldoRepo.CreateSaldoAjusteHistorialAsync(historial, transaction);

                transaction.Commit();
                _logger.LogInformation("Ajuste manual de saldo realizado para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa} por Usuario ID {IdUsuarioAjuste}. Monto: {MontoAjuste}",
                    ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, idUsuarioAjuste, ajusteDto.MontoAjuste);

                var saldoDtoActualizado = await MapToGestionDto(saldoDespuesDeModificacion);
                return (true, null, saldoDtoActualizado);
            }
            catch (BloqueoPorPeriodoCerradoException)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync (Bloqueo)."); }
                throw;
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync."); }
                _logger.LogError(ex, "Error en RealizarAjusteManualSaldoAsync.");
                return (false, $"Error interno al realizar el ajuste: {ex.Message}", null);
            }
            finally
            {
                if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); }
            }
        }

        public async Task<IEnumerable<SaldoAjusteHistorialDto>> ObtenerHistorialAjustesAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico,
        string? destino, int? idDestino, int? idEmpresa)
        {
            var historialData = await _saldoRepo.GetHistorialAjustesAsync(fechaDesde, fechaHasta, idUsuarioModifico, destino, idDestino, idEmpresa);

            return historialData.Select(h => new SaldoAjusteHistorialDto
            {
                IdSaldoAjusteHist = h.Historial.IdSaldoAjusteHist,
                Destino = h.Historial.Destino,
                Id_Destino = h.Historial.IdDestino,
                Id_Empresa = h.Historial.IdEmpresa,
                MontoAjuste = h.Historial.MontoAjuste,
                SaldoAnterior = h.Historial.SaldoAnterior,
                SaldoNuevo = h.Historial.SaldoNuevo,
                Justificacion = h.Historial.Justificacion,
                FechaAjuste = h.Historial.FechaAjuste,
                Id_UsuarioAjuste = h.Historial.IdUsuarioAjuste,
                NombreUsuarioModifico = h.NombreUsuarioModifico
                // TipoMod es implícito "AjusteManualSaldo"
            }).ToList();
        }
    }
}