using Dapper;
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 NotaCreditoDebitoRepository : INotaCreditoDebitoRepository
    {
        private readonly DbConnectionFactory _cf;
        private readonly ILogger<NotaCreditoDebitoRepository> _log;

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

        private string SelectQueryBase() => @"
            SELECT 
                Id_Nota AS IdNota, Destino, Id_Destino AS IdDestino, Referencia, Tipo, Fecha, 
                Monto, Observaciones, Id_Empresa AS IdEmpresa
            FROM dbo.cue_CreditosDebitos";

        public async Task<IEnumerable<NotaCreditoDebito>> GetAllAsync(
            DateTime? fechaDesde, DateTime? fechaHasta,
            string? destino, int? idDestino, int? idEmpresa, string? tipoNota)
        {
            var sqlBuilder = new StringBuilder(SelectQueryBase());
            sqlBuilder.Append(" WHERE 1=1");
            var parameters = new DynamicParameters();

            if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
            if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
            if (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); }
            if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
            if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
            if (!string.IsNullOrWhiteSpace(tipoNota)) { sqlBuilder.Append(" AND Tipo = @TipoParam"); parameters.Add("TipoParam", tipoNota); }

            sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Nota DESC;");

            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QueryAsync<NotaCreditoDebito>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener todas las Notas de Crédito/Débito.");
                return Enumerable.Empty<NotaCreditoDebito>();
            }
        }

        public async Task<NotaCreditoDebito?> GetByIdAsync(int idNota)
        {
            var sql = SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam";
            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<NotaCreditoDebito>(sql, new { IdNotaParam = idNota });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener Nota C/D por ID: {IdNota}", idNota);
                return null;
            }
        }

        public async Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction)
        {
            const string sqlInsert = @"
                INSERT INTO dbo.cue_CreditosDebitos (Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa)
                OUTPUT INSERTED.Id_Nota AS IdNota, INSERTED.Destino, INSERTED.Id_Destino AS IdDestino, INSERTED.Referencia, 
                       INSERTED.Tipo, INSERTED.Fecha, INSERTED.Monto, INSERTED.Observaciones, INSERTED.Id_Empresa AS IdEmpresa
                VALUES (@Destino, @IdDestino, @Referencia, @Tipo, @Fecha, @Monto, @Observaciones, @IdEmpresa);";
            const string sqlHistorico = @"
                INSERT INTO dbo.cue_CreditosDebitos_H 
                    (Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            var inserted = await transaction.Connection!.QuerySingleAsync<NotaCreditoDebito>(sqlInsert, nuevaNota, transaction);
            if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado.");

            await transaction.Connection!.ExecuteAsync(sqlHistorico, new
            {
                IdNotaHist = inserted.IdNota,
                DestinoHist = inserted.Destino,
                IdDestinoHist = inserted.IdDestino,
                ReferenciaHist = inserted.Referencia,
                TipoHist = inserted.Tipo,
                FechaHist = inserted.Fecha,
                MontoHist = inserted.Monto,
                ObservacionesHist = inserted.Observaciones,
                IdEmpresaHist = inserted.IdEmpresa,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Creada"
            }, transaction);
            return inserted;
        }

        public async Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
                SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
                new { IdNotaParam = notaAActualizar.IdNota }, transaction);
            if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada.");

            // Solo se permite actualizar Monto y Observaciones
            const string sqlUpdate = @"
                UPDATE dbo.cue_CreditosDebitos SET
                    Monto = @Monto, Observaciones = @Observaciones
                WHERE Id_Nota = @IdNota;";
            const string sqlHistorico = @"
                INSERT INTO dbo.cue_CreditosDebitos_H 
                    (Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlHistorico, new
            {
                IdNotaHist = actual.IdNota,
                DestinoHist = actual.Destino,
                IdDestinoHist = actual.IdDestino,
                ReferenciaHist = actual.Referencia,
                TipoHist = actual.Tipo,
                FechaHist = actual.Fecha,
                MontoHist = actual.Monto, // Valor ANTERIOR
                ObservacionesHist = actual.Observaciones,
                IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Actualizada"
            }, transaction);

            var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
            {
                notaAActualizar.Monto,
                notaAActualizar.Observaciones,
                notaAActualizar.IdNota
            }, transaction);
            return rowsAffected == 1;
        }

        public async Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
               SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
               new { IdNotaParam = idNota }, transaction);
            if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada para eliminar.");

            const string sqlDelete = "DELETE FROM dbo.cue_CreditosDebitos WHERE Id_Nota = @IdNotaParam";
            const string sqlHistorico = @"
                 INSERT INTO dbo.cue_CreditosDebitos_H 
                    (Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlHistorico, new
            {
                IdNotaHist = actual.IdNota,
                DestinoHist = actual.Destino,
                IdDestinoHist = actual.IdDestino,
                ReferenciaHist = actual.Referencia,
                TipoHist = actual.Tipo,
                FechaHist = actual.Fecha,
                MontoHist = actual.Monto,
                ObservacionesHist = actual.Observaciones,
                IdEmpresaHist = actual.IdEmpresa,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Eliminada"
            }, transaction);

            var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction);
            return rowsAffected == 1;
        }

        public async Task<IEnumerable<(NotaCreditoDebitoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idNotaOriginal)
        {
            using var connection = _cf.CreateConnection();
            var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.Id_Nota, h.Destino, h.Id_Destino, h.Referencia, h.Tipo, h.Fecha, h.Monto,
                h.Observaciones, h.Id_Empresa,
                h.Id_Usuario, h.FechaMod, h.TipoMod,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.cue_CreditosDebitos_H h
            JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
            WHERE 1=1");

            var parameters = new DynamicParameters();

            if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
            if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
            if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
            if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
            if (idNotaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Nota = @IdNotaOriginalParam"); parameters.Add("IdNotaOriginalParam", idNotaOriginal.Value); }

            sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");

            try
            {
                var result = await connection.QueryAsync<NotaCreditoDebitoHistorico, string, (NotaCreditoDebitoHistorico, string)>(
                    sqlBuilder.ToString(),
                    (hist, userName) => (hist, userName),
                    parameters,
                    splitOn: "NombreUsuarioModifico"
                );
                return result;
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener historial de Notas C/D.");
                return Enumerable.Empty<(NotaCreditoDebitoHistorico, string)>();
            }
        }
    }
}