// src/Services/Distribucion/DistribuidorService.cs
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Services.Distribucion
{
    public class DistribuidorService : IDistribuidorService
    {
        private readonly IDistribuidorRepository _distribuidorRepository;
        private readonly ISaldoRepository _saldoRepository;
        private readonly IEmpresaRepository _empresaRepository;
        private readonly IZonaRepository _zonaRepository;
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<DistribuidorService> _logger;

        public DistribuidorService(
            IDistribuidorRepository distribuidorRepository,
            ISaldoRepository saldoRepository,
            IEmpresaRepository empresaRepository,
            IZonaRepository zonaRepository,
            DbConnectionFactory connectionFactory,
            ILogger<DistribuidorService> logger)
        {
            _distribuidorRepository = distribuidorRepository;
            _saldoRepository = saldoRepository;
            _empresaRepository = empresaRepository;
            _zonaRepository = zonaRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        private DistribuidorDto? MapToDto((Distribuidor? Distribuidor, string? NombreZona) data)
        {
            if (data.Distribuidor == null) return null;

            return new DistribuidorDto
            {
                IdDistribuidor = data.Distribuidor.IdDistribuidor,
                Nombre = data.Distribuidor.Nombre,
                Contacto = data.Distribuidor.Contacto,
                NroDoc = data.Distribuidor.NroDoc,
                IdZona = data.Distribuidor.IdZona,
                NombreZona = data.NombreZona, // Ya es anulable en el DTO y en la tupla
                Calle = data.Distribuidor.Calle,
                Numero = data.Distribuidor.Numero,
                Piso = data.Distribuidor.Piso,
                Depto = data.Distribuidor.Depto,
                Telefono = data.Distribuidor.Telefono,
                Email = data.Distribuidor.Email,
                Localidad = data.Distribuidor.Localidad,
                Baja = data.Distribuidor.Baja,
                FechaBaja = data.Distribuidor.FechaBaja
            };
        }

        public async Task<IEnumerable<DistribuidorDto>> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true)
        {
            var data = await _distribuidorRepository.GetAllAsync(nombreFilter, nroDocFilter, soloActivos);
            // Filtrar nulos y asegurar al compilador que no hay nulos en la lista final
            return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
        }

        public async Task<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync(bool? soloActivos = true)
        {
            var data = await _distribuidorRepository.GetAllDropdownAsync(soloActivos);
            // Asegurar que el resultado no sea nulo y no contiene elementos nulos
            if (data == null)
            {
                return new List<DistribuidorDropdownDto>
                {
                    new DistribuidorDropdownDto { IdDistribuidor = 0, Nombre = "No hay distribuidores disponibles" }
                };
            }
            return data.Where(x => x != null)!;
        }

        public async Task<DistribuidorDto?> ObtenerPorIdAsync(int id)
        {
            var data = await _distribuidorRepository.GetByIdAsync(id);
            // MapToDto ahora devuelve DistribuidorDto?
            return MapToDto(data);
        }

        public async Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id)
        {
            var data = await _distribuidorRepository.ObtenerLookupPorIdAsync(id);
            return data;
        }

        public async Task<(DistribuidorDto? Distribuidor, string? Error)> CrearAsync(CreateDistribuidorDto createDto, int idUsuario)
        {
            if (await _distribuidorRepository.ExistsByNroDocAsync(createDto.NroDoc))
                return (null, "El número de documento ya existe para otro distribuidor.");
            if (await _distribuidorRepository.ExistsByNameAsync(createDto.Nombre))
                return (null, "El nombre del distribuidor ya existe.");
            if (createDto.IdZona.HasValue && await _zonaRepository.GetByIdAsync(createDto.IdZona.Value) == null)
                return (null, "La zona seleccionada no es válida o no está activa.");

            var nuevoDistribuidor = new Distribuidor
            {
                Nombre = createDto.Nombre,
                Contacto = createDto.Contacto,
                NroDoc = createDto.NroDoc,
                IdZona = createDto.IdZona,
                Calle = createDto.Calle,
                Numero = createDto.Numero,
                Piso = createDto.Piso,
                Depto = createDto.Depto,
                Telefono = createDto.Telefono,
                Email = createDto.Email,
                Localidad = createDto.Localidad
            };

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();

            try
            {
                var distribuidorCreado = await _distribuidorRepository.CreateAsync(nuevoDistribuidor, idUsuario, transaction);
                if (distribuidorCreado == null) throw new DataException("Error al crear el distribuidor.");

                var empresas = await _empresaRepository.GetAllAsync(null, null);
                foreach (var empresa in empresas)
                {
                    bool saldoCreado = await _saldoRepository.CreateSaldoInicialAsync("Distribuidores", distribuidorCreado.IdDistribuidor, empresa.IdEmpresa, transaction);
                    if (!saldoCreado)
                    {
                        throw new DataException($"Falló al crear saldo inicial para el distribuidor {distribuidorCreado.IdDistribuidor} en la empresa {empresa.IdEmpresa}.");
                    }
                }

                transaction.Commit();
                var dataCompleta = await _distribuidorRepository.GetByIdAsync(distribuidorCreado.IdDistribuidor);
                _logger.LogInformation("Distribuidor ID {IdDistribuidor} creado por Usuario ID {IdUsuario}.", distribuidorCreado.IdDistribuidor, idUsuario);
                // MapToDto ahora devuelve DistribuidorDto?
                return (MapToDto(dataCompleta), null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error CrearAsync Distribuidor: {Nombre}", createDto.Nombre);
                return (null, $"Error interno al crear el distribuidor: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateDistribuidorDto updateDto, int idUsuario)
        {
            var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id);
            if (distribuidorExistente == null) return (false, "Distribuidor no encontrado.");

            if (await _distribuidorRepository.ExistsByNroDocAsync(updateDto.NroDoc, id))
                return (false, "El número de documento ya existe para otro distribuidor.");
            // CORREGIDO: La tupla de retorno para la validación de nombre debe ser (bool, string?)
            if (await _distribuidorRepository.ExistsByNameAsync(updateDto.Nombre, id))
                return (false, "El nombre del distribuidor ya existe."); // Antes (null, ...)
            if (updateDto.IdZona.HasValue && await _zonaRepository.GetByIdAsync(updateDto.IdZona.Value) == null)
                return (false, "La zona seleccionada no es válida o no está activa.");

            distribuidorExistente.Nombre = updateDto.Nombre; distribuidorExistente.Contacto = updateDto.Contacto;
            distribuidorExistente.NroDoc = updateDto.NroDoc; distribuidorExistente.IdZona = updateDto.IdZona;
            distribuidorExistente.Calle = updateDto.Calle; distribuidorExistente.Numero = updateDto.Numero;
            distribuidorExistente.Piso = updateDto.Piso; distribuidorExistente.Depto = updateDto.Depto;
            distribuidorExistente.Telefono = updateDto.Telefono; distribuidorExistente.Email = updateDto.Email;
            distribuidorExistente.Localidad = updateDto.Localidad;

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var actualizado = await _distribuidorRepository.UpdateAsync(distribuidorExistente, idUsuario, transaction);
                if (!actualizado) throw new DataException("Error al actualizar.");
                transaction.Commit();
                _logger.LogInformation("Distribuidor ID {IdDistribuidor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error ActualizarAsync Distribuidor ID: {IdDistribuidor}", id);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
        {
            var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id);
            if (distribuidorExistente == null) return (false, "Distribuidor no encontrado.");

            if (await _distribuidorRepository.IsInUseAsync(id))
                return (false, "No se puede eliminar. El distribuidor tiene movimientos o configuraciones asociadas.");

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                // Lógica para eliminar saldos asociados (si se implementa)
                // var saldosEliminados = await _saldoRepository.DeleteSaldosByDestinoAsync("Distribuidores", id, transaction);
                // if (!saldosEliminados) throw new DataException("Error al eliminar saldos del distribuidor.");

                var eliminado = await _distribuidorRepository.DeleteAsync(id, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar.");
                transaction.Commit();
                _logger.LogInformation("Distribuidor ID {IdDistribuidor} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error EliminarAsync Distribuidor ID: {IdDistribuidor}", id);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario)
        {
            var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id);
            if (distribuidorExistente == null) return (false, "Distribuidor no encontrado.");

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var toggled = await _distribuidorRepository.ToggleBajaAsync(id, darDeBaja, fechaBaja, idUsuario, transaction);
                if (!toggled) throw new DataException("Error al cambiar estado de baja.");
                transaction.Commit();
                _logger.LogInformation("Distribuidor ID {IdDistribuidor} dado de {Estado} por Usuario ID {IdUsuario}.", id, darDeBaja ? "baja" : "alta", idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error ToggleBajaAsync Distribuidor ID: {IdDistribuidor}", id);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<IEnumerable<DistribuidorHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idDistribuidorAfectado)
        {
            var historialData = await _distribuidorRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idDistribuidorAfectado);

            // Si necesitas NombreZona, harías el JOIN en el repo o una llamada aquí a _zonaRepository
            return historialData.Select(h => new DistribuidorHistorialDto
            {
                Id_Distribuidor = h.Historial.Id_Distribuidor,
                Nombre = h.Historial.Nombre,
                Contacto = h.Historial.Contacto,
                NroDoc = h.Historial.NroDoc,
                Id_Zona = h.Historial.Id_Zona,
                Calle = h.Historial.Calle,
                Numero = h.Historial.Numero,
                Piso = h.Historial.Piso,
                Depto = h.Historial.Depto,
                Telefono = h.Historial.Telefono,
                Email = h.Historial.Email,
                Localidad = h.Historial.Localidad,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}