using Dapper;
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.Text;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Data.Repositories.Contables
{
    public class CierreCuentaCorrienteRepository : ICierreCuentaCorrienteRepository
    {
        private readonly DbConnectionFactory _cf;
        private readonly ILogger<CierreCuentaCorrienteRepository> _log;

        public CierreCuentaCorrienteRepository(DbConnectionFactory cf, ILogger<CierreCuentaCorrienteRepository> log)
        {
            _cf = cf;
            _log = log;
        }

        // Aliases SELECT — mapean Id_X (SQL) → IdX (modelo C#).
        private const string SelectModelBase = @"
            SELECT
                Id_Cierre               AS IdCierre,
                Id_Distribuidor         AS IdDistribuidor,
                Id_Empresa              AS IdEmpresa,
                FechaCorte,
                FechaCierre,
                SaldoCierre,
                Estado,
                Justificacion,
                Id_Usuario_Cierre       AS IdUsuarioCierre,
                Id_Usuario_Anula        AS IdUsuarioAnula,
                FechaAnulacion,
                Justificacion_Anulacion AS JustificacionAnulacion
            FROM dbo.cue_CierresCuentaCorriente";

        public async Task<CierreCuentaCorriente?> GetByIdAsync(int idCierre)
        {
            var sql = SelectModelBase + " WHERE Id_Cierre = @IdCierreParam;";
            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, new { IdCierreParam = idCierre });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener Cierre por ID: {IdCierre}", idCierre);
                return null;
            }
        }

        public async Task<CierreCuentaCorriente?> GetUltimoCierreVigenteAsync(int idDistribuidor, int idEmpresa, IDbTransaction? transaction = null)
        {
            var sql = @"
                SELECT TOP 1
                    Id_Cierre               AS IdCierre,
                    Id_Distribuidor         AS IdDistribuidor,
                    Id_Empresa              AS IdEmpresa,
                    FechaCorte,
                    FechaCierre,
                    SaldoCierre,
                    Estado,
                    Justificacion,
                    Id_Usuario_Cierre       AS IdUsuarioCierre,
                    Id_Usuario_Anula        AS IdUsuarioAnula,
                    FechaAnulacion,
                    Justificacion_Anulacion AS JustificacionAnulacion
                FROM dbo.cue_CierresCuentaCorriente
                WHERE Id_Distribuidor = @IdDist
                  AND Id_Empresa     = @IdEmp
                  AND Estado         = 'Activo'
                ORDER BY FechaCorte DESC, Id_Cierre DESC;";

            var parameters = new { IdDist = idDistribuidor, IdEmp = idEmpresa };

            try
            {
                if (transaction != null)
                {
                    return await transaction.Connection!.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, parameters, transaction);
                }

                using var connection = _cf.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, parameters);
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener último cierre vigente Dist={IdDist} Emp={IdEmp}", idDistribuidor, idEmpresa);
                if (transaction != null) throw;
                return null;
            }
        }

        public async Task<CierreCuentaCorriente?> GetCierreVigenteParaFechaAsync(int idDistribuidor, int idEmpresa, DateTime fechaOperacion)
        {
            // Cae en período cerrado si existe un cierre Activo cuya FechaCorte sea >= fechaOperacion (la fecha está dentro del período cerrado).
            // Se devuelve el más reciente (TOP 1 ORDER BY FechaCorte DESC) — el más restrictivo desde la perspectiva de la fecha consultada.
            const string sql = @"
                SELECT TOP 1
                    Id_Cierre               AS IdCierre,
                    Id_Distribuidor         AS IdDistribuidor,
                    Id_Empresa              AS IdEmpresa,
                    FechaCorte,
                    FechaCierre,
                    SaldoCierre,
                    Estado,
                    Justificacion,
                    Id_Usuario_Cierre       AS IdUsuarioCierre,
                    Id_Usuario_Anula        AS IdUsuarioAnula,
                    FechaAnulacion,
                    Justificacion_Anulacion AS JustificacionAnulacion
                FROM dbo.cue_CierresCuentaCorriente
                WHERE Id_Distribuidor = @IdDist
                  AND Id_Empresa     = @IdEmp
                  AND Estado         = 'Activo'
                  AND FechaCorte    >= @FechaOp
                ORDER BY FechaCorte DESC, Id_Cierre DESC;";
            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, new
                {
                    IdDist = idDistribuidor,
                    IdEmp = idEmpresa,
                    FechaOp = fechaOperacion.Date
                });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error en GetCierreVigenteParaFechaAsync Dist={IdDist} Emp={IdEmp} Fecha={Fecha}", idDistribuidor, idEmpresa, fechaOperacion);
                return null;
            }
        }

        public async Task<bool> ExisteCierrePosteriorVigenteAsync(int idDistribuidor, int idEmpresa, DateTime fechaCorte, int? excluirIdCierre = null, IDbTransaction? transaction = null)
        {
            const string sql = @"
                SELECT CASE WHEN EXISTS (
                    SELECT 1 FROM dbo.cue_CierresCuentaCorriente
                    WHERE Id_Distribuidor = @IdDist
                      AND Id_Empresa     = @IdEmp
                      AND Estado         = 'Activo'
                      AND FechaCorte     > @FechaCorte
                      AND (@Excluir IS NULL OR Id_Cierre <> @Excluir)
                ) THEN 1 ELSE 0 END;";

            var parameters = new
            {
                IdDist = idDistribuidor,
                IdEmp = idEmpresa,
                FechaCorte = fechaCorte.Date,
                Excluir = excluirIdCierre
            };

            if (transaction != null)
            {
                return await transaction.Connection!.ExecuteScalarAsync<bool>(sql, parameters, transaction);
            }

            using var connection = _cf.CreateConnection();
            return await connection.ExecuteScalarAsync<bool>(sql, parameters);
        }

        public async Task<int> CreateAsync(CierreCuentaCorriente cierre, int idUsuarioMod, IDbTransaction transaction)
        {
            const string sqlInsertMaster = @"
                INSERT INTO dbo.cue_CierresCuentaCorriente
                    (Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre, Estado, Justificacion, Id_Usuario_Cierre)
                VALUES
                    (@IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre, @Estado, @Justificacion, @IdUsuarioCierre);
                SELECT CAST(SCOPE_IDENTITY() AS INT);";

            var idCierre = await transaction.Connection!.ExecuteScalarAsync<int>(sqlInsertMaster, new
            {
                cierre.IdDistribuidor,
                cierre.IdEmpresa,
                FechaCorte = cierre.FechaCorte.Date,
                cierre.FechaCierre,
                cierre.SaldoCierre,
                cierre.Estado,
                cierre.Justificacion,
                cierre.IdUsuarioCierre
            }, transaction);

            if (idCierre <= 0) throw new DataException("No se pudo crear el cierre — SCOPE_IDENTITY no devolvió valor.");

            const string sqlInsertHist = @"
                INSERT INTO dbo.cue_CierresCuentaCorriente_H
                    (Id_Cierre, Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre,
                     Estado, Justificacion, Id_Usuario_Cierre,
                     Id_Usuario_Anula, FechaAnulacion, Justificacion_Anulacion,
                     TipoMod, Id_Usuario_Mod, FechaMod)
                VALUES
                    (@IdCierre, @IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre,
                     @Estado, @Justificacion, @IdUsuarioCierre,
                     NULL, NULL, NULL,
                     @TipoMod, @IdUsuarioMod, @FechaMod);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHist, new
            {
                IdCierre = idCierre,
                cierre.IdDistribuidor,
                cierre.IdEmpresa,
                FechaCorte = cierre.FechaCorte.Date,
                cierre.FechaCierre,
                cierre.SaldoCierre,
                cierre.Estado,
                cierre.Justificacion,
                cierre.IdUsuarioCierre,
                TipoMod = "Creacion",
                IdUsuarioMod = idUsuarioMod,
                FechaMod = DateTime.Now
            }, transaction);

            return idCierre;
        }

        public async Task<bool> AnularAsync(int idCierre, int idUsuarioAnula, string justificacionAnulacion, int idUsuarioMod, IDbTransaction transaction)
        {
            // UPDATE atómico: solo cambia Estado si está actualmente Activo. Evita doble anulación concurrente.
            const string sqlUpdate = @"
                UPDATE dbo.cue_CierresCuentaCorriente
                   SET Estado                  = 'Anulado',
                       Id_Usuario_Anula        = @IdUsuarioAnula,
                       FechaAnulacion          = @FechaAnulacion,
                       Justificacion_Anulacion = @JustificacionAnulacion
                 WHERE Id_Cierre = @IdCierre
                   AND Estado    = 'Activo';";

            var fechaAnulacion = DateTime.Now;

            int affected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
            {
                IdCierre = idCierre,
                IdUsuarioAnula = idUsuarioAnula,
                FechaAnulacion = fechaAnulacion,
                JustificacionAnulacion = justificacionAnulacion
            }, transaction);

            if (affected != 1) return false;

            // Snapshot post-update para el _H. Trae los valores ya actualizados.
            const string sqlSnapshot = SelectModelBase + " WHERE Id_Cierre = @IdCierre;";
            var actualizado = await transaction.Connection!.QuerySingleAsync<CierreCuentaCorriente>(sqlSnapshot, new { IdCierre = idCierre }, transaction);

            const string sqlInsertHist = @"
                INSERT INTO dbo.cue_CierresCuentaCorriente_H
                    (Id_Cierre, Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre,
                     Estado, Justificacion, Id_Usuario_Cierre,
                     Id_Usuario_Anula, FechaAnulacion, Justificacion_Anulacion,
                     TipoMod, Id_Usuario_Mod, FechaMod)
                VALUES
                    (@IdCierre, @IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre,
                     @Estado, @Justificacion, @IdUsuarioCierre,
                     @IdUsuarioAnula, @FechaAnulacion, @JustificacionAnulacion,
                     @TipoMod, @IdUsuarioMod, @FechaMod);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHist, new
            {
                IdCierre = actualizado.IdCierre,
                actualizado.IdDistribuidor,
                actualizado.IdEmpresa,
                FechaCorte = actualizado.FechaCorte.Date,
                actualizado.FechaCierre,
                actualizado.SaldoCierre,
                actualizado.Estado,
                actualizado.Justificacion,
                actualizado.IdUsuarioCierre,
                actualizado.IdUsuarioAnula,
                actualizado.FechaAnulacion,
                JustificacionAnulacion = actualizado.JustificacionAnulacion,
                TipoMod = "Reapertura",
                IdUsuarioMod = idUsuarioMod,
                FechaMod = DateTime.Now
            }, transaction);

            return true;
        }

        public async Task<IEnumerable<CierreCuentaCorrienteDto>> GetAllAsync(
            int? idDistribuidor, int? idEmpresa, string? estado,
            DateTime? fechaCorteDesde, DateTime? fechaCorteHasta)
        {
            var sqlBuilder = new StringBuilder(@"
                SELECT
                    c.Id_Cierre               AS IdCierre,
                    c.Id_Distribuidor         AS IdDistribuidor,
                    d.Nombre                  AS NombreDistribuidor,
                    c.Id_Empresa              AS IdEmpresa,
                    e.Nombre                  AS NombreEmpresa,
                    CONVERT(varchar(10), c.FechaCorte, 23) AS FechaCorte,
                    c.FechaCierre,
                    c.SaldoCierre,
                    c.Estado,
                    c.Justificacion,
                    c.Id_Usuario_Cierre       AS IdUsuarioCierre,
                    (uc.Nombre + ' ' + uc.Apellido) AS NombreUsuarioCierre,
                    c.Id_Usuario_Anula        AS IdUsuarioAnula,
                    CASE WHEN ua.Id IS NULL THEN NULL ELSE (ua.Nombre + ' ' + ua.Apellido) END AS NombreUsuarioAnula,
                    c.FechaAnulacion,
                    c.Justificacion_Anulacion AS JustificacionAnulacion,
                    CAST(CASE WHEN c.Estado = 'Activo'
                              AND c.Id_Cierre = (
                                  SELECT TOP 1 c2.Id_Cierre FROM dbo.cue_CierresCuentaCorriente c2
                                  WHERE c2.Id_Distribuidor = c.Id_Distribuidor
                                    AND c2.Id_Empresa     = c.Id_Empresa
                                    AND c2.Estado         = 'Activo'
                                  ORDER BY c2.FechaCorte DESC, c2.Id_Cierre DESC)
                         THEN 1 ELSE 0 END AS bit) AS EsUltimoVigente
                FROM dbo.cue_CierresCuentaCorriente c
                JOIN dbo.dist_dtDistribuidores d ON d.Id_Distribuidor = c.Id_Distribuidor
                JOIN dbo.dist_dtEmpresas       e ON e.Id_Empresa     = c.Id_Empresa
                JOIN dbo.gral_Usuarios        uc ON uc.Id            = c.Id_Usuario_Cierre
                LEFT JOIN dbo.gral_Usuarios   ua ON ua.Id            = c.Id_Usuario_Anula
                WHERE 1=1");

            var parameters = new DynamicParameters();
            if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND c.Id_Distribuidor = @IdDist"); parameters.Add("IdDist", idDistribuidor.Value); }
            if (idEmpresa.HasValue) { sqlBuilder.Append(" AND c.Id_Empresa = @IdEmp"); parameters.Add("IdEmp", idEmpresa.Value); }
            if (!string.IsNullOrWhiteSpace(estado)) { sqlBuilder.Append(" AND c.Estado = @Estado"); parameters.Add("Estado", estado); }
            if (fechaCorteDesde.HasValue) { sqlBuilder.Append(" AND c.FechaCorte >= @FechaDesde"); parameters.Add("FechaDesde", fechaCorteDesde.Value.Date); }
            if (fechaCorteHasta.HasValue) { sqlBuilder.Append(" AND c.FechaCorte <= @FechaHasta"); parameters.Add("FechaHasta", fechaCorteHasta.Value.Date); }
            sqlBuilder.Append(" ORDER BY c.FechaCorte DESC, c.Id_Cierre DESC;");

            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QueryAsync<CierreCuentaCorrienteDto>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error en GetAllAsync de CierresCuentaCorriente.");
                return Enumerable.Empty<CierreCuentaCorrienteDto>();
            }
        }

        public async Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> GetHistorialAsync(int idCierre)
        {
            const string sql = @"
                SELECT
                    h.Id_Historial,
                    h.Id_Cierre,
                    h.Id_Distribuidor,
                    h.Id_Empresa,
                    h.FechaCorte,
                    h.FechaCierre,
                    h.SaldoCierre,
                    h.Estado,
                    h.Justificacion,
                    h.Id_Usuario_Cierre,
                    h.Id_Usuario_Anula,
                    h.FechaAnulacion,
                    h.Justificacion_Anulacion,
                    h.TipoMod,
                    h.Id_Usuario_Mod,
                    (u.Nombre + ' ' + u.Apellido) AS NombreUsuarioModifico,
                    h.FechaMod
                FROM dbo.cue_CierresCuentaCorriente_H h
                JOIN dbo.gral_Usuarios u ON u.Id = h.Id_Usuario_Mod
                WHERE h.Id_Cierre = @IdCierre
                ORDER BY h.FechaMod ASC, h.Id_Historial ASC;";

            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QueryAsync<CierreCuentaCorrienteHistorialDto>(sql, new { IdCierre = idCierre });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error en GetHistorialAsync para Cierre ID {IdCierre}", idCierre);
                return Enumerable.Empty<CierreCuentaCorrienteHistorialDto>();
            }
        }

        public async Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> ObtenerHistorialAsync(
            DateTime? fechaDesde, DateTime? fechaHasta,
            int? idUsuarioModifico, string? tipoModificacion,
            int? idCierreAfectado)
        {
            // FechaMod cubre el rango inclusive: [fechaDesde 00:00, fechaHasta+1día). Patrón consistente con otros ObtenerHistorial del proyecto.
            var sql = @"
                SELECT
                    h.Id_Historial,
                    h.Id_Cierre,
                    h.Id_Distribuidor,
                    h.Id_Empresa,
                    h.FechaCorte,
                    h.FechaCierre,
                    h.SaldoCierre,
                    h.Estado,
                    h.Justificacion,
                    h.Id_Usuario_Cierre,
                    h.Id_Usuario_Anula,
                    h.FechaAnulacion,
                    h.Justificacion_Anulacion,
                    h.TipoMod,
                    h.Id_Usuario_Mod,
                    (u.Nombre + ' ' + u.Apellido) AS NombreUsuarioModifico,
                    h.FechaMod
                FROM dbo.cue_CierresCuentaCorriente_H h
                JOIN dbo.gral_Usuarios u ON u.Id = h.Id_Usuario_Mod
                WHERE 1 = 1
                  AND (@FechaDesde IS NULL OR h.FechaMod >= @FechaDesde)
                  AND (@FechaHasta IS NULL OR h.FechaMod <  DATEADD(DAY, 1, @FechaHasta))
                  AND (@IdUsuarioMod IS NULL OR h.Id_Usuario_Mod = @IdUsuarioMod)
                  AND (@TipoMod IS NULL OR h.TipoMod = @TipoMod)
                  AND (@IdCierre IS NULL OR h.Id_Cierre = @IdCierre)
                ORDER BY h.FechaMod DESC, h.Id_Historial DESC;";

            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QueryAsync<CierreCuentaCorrienteHistorialDto>(sql, new
                {
                    FechaDesde = fechaDesde?.Date,
                    FechaHasta = fechaHasta?.Date,
                    IdUsuarioMod = idUsuarioModifico,
                    TipoMod = tipoModificacion,
                    IdCierre = idCierreAfectado
                });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error en ObtenerHistorialAsync (auditoría) de Cierres CC.");
                return Enumerable.Empty<CierreCuentaCorrienteHistorialDto>();
            }
        }
    }
}
