using Dapper;
using GestionIntegral.Api.Data.Repositories;
using GestionIntegral.Api.Dtos.Empresas;
using GestionIntegral.Api.Models.Distribucion;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
  public class EmpresaRepository : IEmpresaRepository
  {
    private readonly DbConnectionFactory _connectionFactory;
    private readonly ILogger<EmpresaRepository> _logger;

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

    // --- Métodos de Lectura (no necesitan transacción explícita aquí) ---

    public async Task<IEnumerable<Empresa>> GetAllAsync(string? nombreFilter, string? detalleFilter)
    {
      var sqlBuilder = new StringBuilder("SELECT Id_Empresa AS IdEmpresa, Nombre, Detalle FROM dbo.dist_dtEmpresas WHERE 1=1");
      var parameters = new DynamicParameters();

      if (!string.IsNullOrWhiteSpace(nombreFilter))
      {
        sqlBuilder.Append(" AND Nombre LIKE @NombreFilter");
        parameters.Add("NombreFilter", $"%{nombreFilter}%");
      }
      if (!string.IsNullOrWhiteSpace(detalleFilter))
      {
        sqlBuilder.Append(" AND Detalle LIKE @DetalleFilter");
        parameters.Add("DetalleFilter", $"%{detalleFilter}%");
      }
      sqlBuilder.Append(" ORDER BY Nombre;");

      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          return await connection.QueryAsync<Empresa>(sqlBuilder.ToString(), parameters);
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error al obtener todas las Empresas.");
        return Enumerable.Empty<Empresa>();
      }
    }

    public async Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync()
    {
      var sqlBuilder = new StringBuilder("SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE 1=1");
      var parameters = new DynamicParameters();
      sqlBuilder.Append(" ORDER BY Nombre;");
      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          return await connection.QueryAsync<EmpresaDropdownDto>(sqlBuilder.ToString(), parameters);
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error al obtener todas las Empresas.");
        return Enumerable.Empty<EmpresaDropdownDto>();
      }
    }

    public async Task<Empresa?> GetByIdAsync(int id)
    {
      var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre, Detalle FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          return await connection.QuerySingleOrDefaultAsync<Empresa>(sql, new { Id = id });
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error al obtener Empresa por ID: {IdEmpresa}", id);
        return null;
      }
    }

    public async Task<Empresa?> ObtenerLookupPorIdAsync(int id)
    {
      var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          return await connection.QuerySingleOrDefaultAsync<Empresa>(sql, new { Id = id });
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error al obtener Empresa por ID: {IdEmpresa}", id);
        return null;
      }
    }

    public async Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null)
    {
      var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtEmpresas WHERE Nombre = @Nombre");
      var parameters = new DynamicParameters();
      parameters.Add("Nombre", nombre);

      if (excludeId.HasValue)
      {
        sqlBuilder.Append(" AND Id_Empresa != @ExcludeId");
        parameters.Add("ExcludeId", excludeId.Value);
      }

      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          var count = await connection.ExecuteScalarAsync<int>(sqlBuilder.ToString(), parameters);
          return count > 0;
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error en ExistsByNameAsync para Empresa con nombre: {Nombre}", nombre);
        return true; // Asumir que existe si hay error
      }
    }

    public async Task<bool> IsInUseAsync(int id)
    {
      // Verifica si la empresa está referenciada en dist_dtPublicaciones
      var sql = "SELECT COUNT(1) FROM dbo.dist_dtPublicaciones WHERE Id_Empresa = @IdEmpresa";
      try
      {
        using (var connection = _connectionFactory.CreateConnection())
        {
          var count = await connection.ExecuteScalarAsync<int>(sql, new { IdEmpresa = id });
          return count > 0;
        }
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Error en IsInUseAsync para Empresa ID: {IdEmpresa}", id);
        return true; // Asumir en uso si hay error
      }
    }

    // --- Métodos de Escritura (USAN TRANSACCIÓN PASADA DESDE EL SERVICIO) ---

    public async Task<Empresa?> CreateAsync(Empresa nuevaEmpresa, int idUsuario, IDbTransaction transaction)
    {
      var sqlInsert = @"
                INSERT INTO dbo.dist_dtEmpresas (Nombre, Detalle)
                OUTPUT INSERTED.Id_Empresa AS IdEmpresa, INSERTED.Nombre, INSERTED.Detalle
                VALUES (@Nombre, @Detalle);";

      var sqlInsertHistorico = @"
                INSERT INTO dbo.dist_dtEmpresas_H (Id_Empresa, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdEmpresa, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";

      // La ejecución y manejo de errores/commit/rollback se hará en el SERVICIO
      // Aquí solo ejecutamos los comandos DENTRO de la transacción existente.

      // Obtener el ID y datos insertados
      var insertedEmpresa = await transaction.Connection!.QuerySingleAsync<Empresa>(
        sqlInsert,
        new { nuevaEmpresa.Nombre, nuevaEmpresa.Detalle },
        transaction: transaction
      );

      if (insertedEmpresa == null || insertedEmpresa.IdEmpresa <= 0)
      {
        throw new Exception("No se pudo obtener el ID de la empresa insertada.");
      }

      // Insertar en historial
      await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
      {
        IdEmpresa = insertedEmpresa.IdEmpresa,
        insertedEmpresa.Nombre,
        insertedEmpresa.Detalle,
        IdUsuario = idUsuario,
        FechaMod = DateTime.Now,
        TipoMod = "Insertada"
      }, transaction: transaction);

      return insertedEmpresa; // Devolver la entidad con el ID
    }

    public async Task<bool> UpdateAsync(Empresa empresaAActualizar, int idUsuario, IDbTransaction transaction)
    {
      // El servicio ya verificó que existe. Obtenemos estado actual para historial.
      var empresaActual = await GetByIdAsync(empresaAActualizar.IdEmpresa); // Podría fallar si ya no existe, controlar en servicio
      if (empresaActual == null) throw new InvalidOperationException("No se encontró la empresa para obtener datos para el historial de actualización.");

      var sqlUpdate = @"
                UPDATE dbo.dist_dtEmpresas
                SET Nombre = @Nombre, Detalle = @Detalle
                WHERE Id_Empresa = @IdEmpresa;";

      var sqlInsertHistorico = @"
                INSERT INTO dbo.dist_dtEmpresas_H (Id_Empresa, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdEmpresa, @NombreActual, @DetalleActual, @IdUsuario, @FechaMod, @TipoMod);";

      // Insertar en historial (estado anterior)
      await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
      {
        IdEmpresa = empresaActual.IdEmpresa,
        NombreActual = empresaActual.Nombre,
        DetalleActual = empresaActual.Detalle,
        IdUsuario = idUsuario,
        FechaMod = DateTime.Now,
        TipoMod = "Modificada"
      }, transaction: transaction);

      // Actualizar principal
      var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
      {
        empresaAActualizar.Nombre,
        empresaAActualizar.Detalle,
        empresaAActualizar.IdEmpresa
      }, transaction: transaction);

      return rowsAffected == 1;
    }

    public async Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction)
    {
      var empresaActual = await GetByIdAsync(id); // Obtener datos para historial
      if (empresaActual == null) throw new InvalidOperationException("No se encontró la empresa para obtener datos para el historial de eliminación.");

      var sqlDelete = "DELETE FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
      var sqlInsertHistorico = @"
                INSERT INTO dbo.dist_dtEmpresas_H (Id_Empresa, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdEmpresa, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";

      // Insertar en historial (estado antes de borrar)
      await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
      {
        IdEmpresa = empresaActual.IdEmpresa,
        empresaActual.Nombre,
        empresaActual.Detalle,
        IdUsuario = idUsuario,
        FechaMod = DateTime.Now,
        TipoMod = "Eliminada"
      }, transaction: transaction);

      // Eliminar de la tabla principal
      var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { Id = id }, transaction: transaction);

      return rowsAffected == 1;
    }

    public async Task<IEnumerable<(EmpresaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idEmpresaOriginal)
    {
      using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf
      var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.Id_Empresa, h.Nombre, h.Detalle,
                h.Id_Usuario, h.FechaMod, h.TipoMod,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.dist_dtEmpresas_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 (idEmpresaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaOriginalParam"); parameters.Add("IdEmpresaOriginalParam", idEmpresaOriginal.Value); }

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

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