using Dapper;
using GestionIntegral.Api.Data.Repositories;
using GestionIntegral.Api.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using GestionIntegral.Api.Models.Contables;
using System.Text;

namespace GestionIntegral.Api.Data.Repositories.Contables
{
    public class SaldoRepository : ISaldoRepository
    {
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<SaldoRepository> _logger;

        public SaldoRepository(DbConnectionFactory connectionFactory, ILogger<SaldoRepository> logger)
        {
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        public async Task<IEnumerable<int>> GetAllDistribuidorIdsAsync()
        {
            var sql = "SELECT Id_Distribuidor FROM dbo.dist_dtDistribuidores WHERE Baja = 0";
            try
            {
                using (var connection = _connectionFactory.CreateConnection())
                {
                    return await connection.QueryAsync<int>(sql);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener IDs de Distribuidores.");
                return Enumerable.Empty<int>();
            }
        }

        public async Task<bool> CreateSaldoInicialAsync(string destino, int idDestino, int idEmpresa, IDbTransaction transaction)
        {
            var sql = @"
                INSERT INTO dbo.cue_Saldos (Destino, Id_Destino, Monto, Id_Empresa)
                VALUES (@Destino, @IdDestino, 0.00, @IdEmpresa);";
            try
            {
                int rowsAffected = await transaction.Connection!.ExecuteAsync(sql, // Añadir !
                    new { Destino = destino, IdDestino = idDestino, IdEmpresa = idEmpresa },
                    transaction: transaction);
                return rowsAffected == 1;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al insertar saldo inicial para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
                throw;
            }
        }

        public async Task<bool> DeleteSaldosByEmpresaAsync(int idEmpresa, IDbTransaction transaction)
        {
            var sql = "DELETE FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
            try
            {
                await transaction.Connection!.ExecuteAsync(sql, new { IdEmpresa = idEmpresa }, transaction: transaction);
                return true; // Asumir éxito si no hay excepción
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al eliminar saldos para Empresa ID {IdEmpresa}.", idEmpresa);
                throw;
            }
        }

        public async Task<bool> ModificarSaldoAsync(string destino, int idDestino, int idEmpresa, decimal montoAAgregar, IDbTransaction? transaction = null)
        {
            var sql = @"UPDATE dbo.cue_Saldos
                 SET Monto = Monto + @MontoAAgregar,
                     FechaUltimaModificacion = @FechaActualizacion -- << AÑADIR
                 WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";

            IDbConnection connection = transaction?.Connection ?? _connectionFactory.CreateConnection();
            bool ownConnection = transaction == null;

            try
            {
                if (ownConnection && connection.State != ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
                }

                var parameters = new
                {
                    MontoAAgregar = montoAAgregar,
                    Destino = destino,
                    IdDestino = idDestino,
                    IdEmpresa = idEmpresa,
                    FechaActualizacion = DateTime.Now // O DateTime.UtcNow si prefieres
                };
                int rowsAffected = await connection.ExecuteAsync(sql, parameters, transaction: transaction);
                return rowsAffected == 1;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al modificar saldo para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
                if (transaction != null) throw;
                return false;
            }
            finally
            {
                if (ownConnection && connection.State == ConnectionState.Open)
                {
                    if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
                }
                if (ownConnection && connection is IDisposable d) d.Dispose(); // Mejorar dispose
            }
        }

        public async Task<bool> CheckIfSaldosExistForEmpresaAsync(int idEmpresa)
        {
            var sql = "SELECT COUNT(1) FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
            try
            {
                // Este método es de solo lectura, no necesita transacción externa normalmente
                using (var connection = _connectionFactory.CreateConnection())
                {
                    var count = await connection.ExecuteScalarAsync<int>(sql, new { IdEmpresa = idEmpresa });
                    return count > 0; // Devuelve true si hay al menos un saldo
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en CheckIfSaldosExistForEmpresaAsync para Empresa ID {IdEmpresa}", idEmpresa);
                return false; // Asumir que no existen si hay error, para no bloquear la eliminación innecesariamente
                              // O podrías devolver true para ser más conservador si la verificación es crítica.
            }
        }

        public async Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
        {
            var sqlBuilder = new StringBuilder(@"
                SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion 
                FROM dbo.cue_Saldos s
                WHERE 1=1");

            var parameters = new DynamicParameters();

            if (!string.IsNullOrWhiteSpace(destinoFilter))
            {
                sqlBuilder.Append(" AND s.Destino = @Destino");
                parameters.Add("Destino", destinoFilter);

                // Filtro para excluir distribuidores de baja si el tipo es Distribuidores
                // No se aplica a Canillas por requerimiento explícito del usuario
                if (destinoFilter == "Distribuidores")
                {
                    sqlBuilder.Append(" AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0)");
                }
            }
            else
            {
                // Si no hay filtro de destino, aplicamos el filtro de baja solo para Distribuidores
                sqlBuilder.Append(@" AND (
                    (s.Destino = 'Distribuidores' AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0))
                    OR (s.Destino != 'Distribuidores')
                )");
            }

            if (idDestinoFilter.HasValue)
            {
                sqlBuilder.Append(" AND s.Id_Destino = @IdDestino");
                parameters.Add("IdDestino", idDestinoFilter.Value);
            }
            if (idEmpresaFilter.HasValue)
            {
                sqlBuilder.Append(" AND s.Id_Empresa = @IdEmpresa");
                parameters.Add("IdEmpresa", idEmpresaFilter.Value);
            }
            sqlBuilder.Append(" ORDER BY s.Destino, s.Id_Empresa, s.Id_Destino;");

            using var connection = _connectionFactory.CreateConnection();
            return await connection.QueryAsync<Saldo>(sqlBuilder.ToString(), parameters);
        }

        public async Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null)
        {
            const string sql = "SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
            var conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
            if (transaction == null && conn.State != ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else conn.Open(); }

            try
            {
                return await conn.QuerySingleOrDefaultAsync<Saldo>(sql, new { Destino = destino, IdDestino = idDestino, IdEmpresa = idEmpresa }, transaction);
            }
            finally
            {
                if (transaction == null && conn.State == ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else conn.Close(); }
            }
        }

        public async Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction)
        {
            const string sql = @"
            INSERT INTO dbo.cue_SaldoAjustesHistorial 
                (Destino, Id_Destino, Id_Empresa, MontoAjuste, SaldoAnterior, SaldoNuevo, Justificacion, FechaAjuste, Id_UsuarioAjuste)
            VALUES 
                (@Destino, @IdDestino, @IdEmpresa, @MontoAjuste, @SaldoAnterior, @SaldoNuevo, @Justificacion, @FechaAjuste, @IdUsuarioAjuste);";

            await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction);
        }

        public async Task<IEnumerable<(SaldoAjusteHistorial Historial, string NombreUsuarioModifico)>> GetHistorialAjustesAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico,
        string? destino, int? idDestino, int? idEmpresa)
    {
        using var connection = _connectionFactory.CreateConnection();
        var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.IdSaldoAjusteHist, h.Destino, h.Id_Destino, h.Id_Empresa, 
                h.MontoAjuste, h.SaldoAnterior, h.SaldoNuevo, h.Justificacion, 
                h.FechaAjuste, h.Id_UsuarioAjuste,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.cue_SaldoAjustesHistorial h
            JOIN dbo.gral_Usuarios u ON h.Id_UsuarioAjuste = u.Id
            WHERE 1=1");
        
        var parameters = new DynamicParameters();

        if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
        if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
        if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_UsuarioAjuste = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
        if (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND h.Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); }
        if (idDestino.HasValue) { sqlBuilder.Append(" AND h.Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
        if (idEmpresa.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
        
        sqlBuilder.Append(" ORDER BY h.FechaAjuste DESC;");

        try
        {
            var result = await connection.QueryAsync<SaldoAjusteHistorial, string, (SaldoAjusteHistorial, string)>(
                sqlBuilder.ToString(),
                (hist, userName) => (hist, userName),
                parameters,
                splitOn: "NombreUsuarioModifico"
            );
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error al obtener historial de Ajustes de Saldo.");
            return Enumerable.Empty<(SaldoAjusteHistorial, string)>();
        }
    }
    }
}