// src/Elecciones.Api/Controllers/ResultadosController.cs
using Elecciones.Core.DTOs.ApiResponses;
using Elecciones.Database;
using Elecciones.Database.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Elecciones.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ResultadosController : ControllerBase
{
    private readonly EleccionesDbContext _dbContext;
    private readonly ILogger<ResultadosController> _logger;
    private readonly IConfiguration _configuration;

    public ResultadosController(EleccionesDbContext dbContext, ILogger<ResultadosController> logger, IConfiguration configuration)
    {
        _dbContext = dbContext;
        _logger = logger;
        _configuration = configuration;
    }

    [HttpGet("partido/{municipioId}")] // Renombramos el parámetro para mayor claridad
    public async Task<IActionResult> GetResultadosPorPartido(string municipioId, [FromQuery] int categoriaId)
    {
        var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking()
            .FirstOrDefaultAsync(a => a.SeccionId == municipioId && a.NivelId == 30);

        if (ambito == null)
        {
            return NotFound(new { message = $"No se encontró el partido con ID {municipioId}" });
        }

        var estadoRecuento = await _dbContext.EstadosRecuentos.AsNoTracking()
        .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id && e.CategoriaId == categoriaId);

        var agrupacionIds = await _dbContext.ResultadosVotos
            .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId)
            .Select(rv => rv.AgrupacionPoliticaId).Distinct().ToListAsync();

        var logosRelevantes = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking()
            .Where(l => l.CategoriaId == categoriaId && // <-- Usamos la categoría del parámetro
                        agrupacionIds.Contains(l.AgrupacionPoliticaId) &&
                        (l.AmbitoGeograficoId == null || l.AmbitoGeograficoId == ambito.Id))
            .ToListAsync();

        var resultadosVotos = await _dbContext.ResultadosVotos.AsNoTracking()
            .Include(rv => rv.AgrupacionPolitica)
            .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId)
            .ToListAsync();

        long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos);

        var respuestaDto = new MunicipioResultadosDto
        {
            MunicipioNombre = ambito.Nombre,
            UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow,
            PorcentajeEscrutado = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0,
            PorcentajeParticipacion = estadoRecuento?.ParticipacionPorcentaje ?? 0,
            Resultados = resultadosVotos.Select(rv =>
            {
                var logoUrl = logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl
                           ?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl;

                return new AgrupacionResultadoDto
                {
                    Id = rv.AgrupacionPolitica.Id,
                    Nombre = rv.AgrupacionPolitica.Nombre,
                    NombreCorto = rv.AgrupacionPolitica.NombreCorto,
                    Color = rv.AgrupacionPolitica.Color,
                    LogoUrl = logoUrl,
                    Votos = rv.CantidadVotos,
                    Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0
                };
            }).OrderByDescending(r => r.Votos).ToList(),
            VotosAdicionales = new VotosAdicionalesDto
            {
                EnBlanco = estadoRecuento?.VotosEnBlanco ?? 0,
                Nulos = estadoRecuento?.VotosNulos ?? 0,
                Recurridos = estadoRecuento?.VotosRecurridos ?? 0
            }
        };

        return Ok(respuestaDto);
    }

    [HttpGet("provincia/{distritoId}")]
    public async Task<IActionResult> GetResultadosProvinciales(string distritoId)
    {
        var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
            .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);

        if (provincia == null)
        {
            return NotFound($"No se encontró la provincia con distritoId {distritoId}");
        }

        var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
            .Include(e => e.CategoriaElectoral)
            .Where(e => e.AmbitoGeograficoId == provincia.Id)
            .ToDictionaryAsync(e => e.CategoriaId);

        var resultadosPorMunicipio = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Include(r => r.AgrupacionPolitica)
            .Where(r => r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio
            .ToListAsync();

        var logos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync();

        // --- LÓGICA DE AGRUPACIÓN Y CÁLCULO CORREGIDA ---
        var resultadosAgrupados = resultadosPorMunicipio
            .GroupBy(r => r.CategoriaId)
            .Select(g => new
            {
                CategoriaId = g.Key,
                CategoriaNombre = estadosPorCategoria.ContainsKey(g.Key) ? estadosPorCategoria[g.Key].CategoriaElectoral.Nombre : "Desconocido",
                EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.Key),
                TotalVotosCategoria = g.Sum(r => r.CantidadVotos),
                // Agrupamos por el ID de la agrupación, no por el objeto, para evitar duplicados
                ResultadosAgrupados = g.GroupBy(r => r.AgrupacionPoliticaId)
                                       .Select(partidoGroup => new
                                       {
                                           Agrupacion = partidoGroup.First().AgrupacionPolitica,
                                           Votos = partidoGroup.Sum(r => r.CantidadVotos)
                                       })
                                       .ToList()
            })
            .Select(g => new
            {
                g.CategoriaId,
                g.CategoriaNombre,
                g.EstadoRecuento,
                Resultados = g.ResultadosAgrupados
                              .Select(r =>
                              {
                                  var logoUrl = logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId != null)?.LogoUrl
                                             ?? logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId == null)?.LogoUrl;

                                  return new
                                  {
                                      Id = r.Agrupacion.Id,
                                      r.Agrupacion.Nombre,
                                      r.Agrupacion.NombreCorto,
                                      r.Agrupacion.Color,
                                      LogoUrl = logoUrl,
                                      r.Votos,
                                      Porcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0
                                  };
                              })
                              .OrderByDescending(r => r.Votos)
                              .ToList()
            })
            .OrderBy(c => c.CategoriaId)
            .ToList();

        return Ok(resultadosAgrupados);
    }


    [HttpGet("bancas/{seccionId}")]
    public async Task<IActionResult> GetBancasPorSeccion(string seccionId)
    {
        // 1. Buscamos el ámbito usando 'SeccionProvincialId'.
        // La API oficial usa este campo para las secciones electorales.
        // Además, el worker guarda estas secciones con NivelId = 20, por lo que lo usamos aquí para consistencia.
        var seccion = await _dbContext.AmbitosGeograficos
            .AsNoTracking()
            .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); // Nivel 20 = Sección Electoral Provincial

        if (seccion == null)
        {
            _logger.LogWarning("No se encontró la sección electoral con SeccionProvincialId: {SeccionId}", seccionId);
            return NotFound(new { message = $"No se encontró la sección electoral con ID {seccionId}" });
        }

        // 2. Buscamos todas las proyecciones para ese ámbito (usando su clave primaria 'Id')
        var proyecciones = await _dbContext.ProyeccionesBancas
            .AsNoTracking()
            .Include(p => p.AgrupacionPolitica)
            .Where(p => p.AmbitoGeograficoId == seccion.Id)
            .Select(p => new
            {
                AgrupacionNombre = p.AgrupacionPolitica.Nombre,
                Bancas = p.NroBancas
            })
            .OrderByDescending(p => p.Bancas)
            .ToListAsync();

        if (!proyecciones.Any())
        {
            // Este caso es posible si aún no hay proyecciones calculadas para esta sección.
            _logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre}", seccion.Nombre);
            return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" });
        }

        // 3. Devolvemos la respuesta
        return Ok(new
        {
            SeccionNombre = seccion.Nombre,
            Proyeccion = proyecciones
        });
    }

    [HttpGet("mapa")]
    public async Task<IActionResult> GetResultadosParaMapa()
    {
        var maxVotosPorAmbito = _dbContext.ResultadosVotos
            .GroupBy(rv => rv.AmbitoGeograficoId)
            .Select(g => new
            {
                AmbitoId = g.Key,
                MaxVotos = g.Max(v => v.CantidadVotos)
            });

        var resultadosGanadores = await _dbContext.ResultadosVotos
    .Join(
        maxVotosPorAmbito,
        voto => new { AmbitoId = voto.AmbitoGeograficoId, Votos = voto.CantidadVotos },
        max => new { AmbitoId = max.AmbitoId, Votos = max.MaxVotos },
        (voto, max) => voto
    )
    .Include(rv => rv.AmbitoGeografico)
    .Include(rv => rv.AgrupacionPolitica)
    .Where(rv => rv.AmbitoGeografico.NivelId == 30)
    .Select(rv => new
    {
        AmbitoId = rv.AmbitoGeografico.Id,
        DepartamentoNombre = rv.AmbitoGeografico.Nombre,
        AgrupacionGanadoraId = rv.AgrupacionPoliticaId,
        ColorGanador = rv.AgrupacionPolitica.Color
    })
    .ToListAsync();

        return Ok(resultadosGanadores);
    }

    [HttpGet("municipio/{ambitoId}")]
    public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId, [FromQuery] int categoriaId)
    {
        _logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}, CategoriaId: {CategoriaId}", ambitoId, categoriaId);

        // Validamos que el ámbito exista
        var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking()
            .FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30);
        if (ambito == null)
        {
            _logger.LogWarning("No se encontró el municipio con ID: {AmbitoId}", ambitoId);
            return NotFound($"No se encontró el municipio con ID {ambitoId}");
        }

        // Obtenemos el estado del recuento para el ámbito
        var estadoRecuento = await _dbContext.EstadosRecuentos
            .AsNoTracking()
            .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id);

        // Obtenemos los votos para ESE municipio y ESA categoría
        var resultadosVotos = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Include(rv => rv.AgrupacionPolitica)
            .Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId)
            .ToListAsync();

        // Calculamos el total de votos solo para esta selección
        var totalVotosPositivos = (decimal)resultadosVotos.Sum(r => r.CantidadVotos);

        // Mapeamos los resultados de los partidos
        var resultadosPartidosDto = resultadosVotos
            .OrderByDescending(r => r.CantidadVotos)
            .Select(rv => new AgrupacionResultadoDto
            {
                Id = rv.AgrupacionPolitica.Id,
                Nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre,
                Color = rv.AgrupacionPolitica.Color,
                Votos = rv.CantidadVotos,
                Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos / totalVotosPositivos) * 100 : 0
            }).ToList();

        // Construimos la respuesta completa del DTO
        var respuestaDto = new MunicipioResultadosDto
        {
            MunicipioNombre = ambito.Nombre,
            UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow, // Use null-conditional operator
            PorcentajeEscrutado = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0,
            PorcentajeParticipacion = estadoRecuento?.ParticipacionPorcentaje ?? 0,
            Resultados = resultadosPartidosDto,
            VotosAdicionales = new VotosAdicionalesDto // Assuming default constructor is fine
            {
                EnBlanco = estadoRecuento?.VotosEnBlanco ?? 0,
                Nulos = estadoRecuento?.VotosNulos ?? 0,
                Recurridos = estadoRecuento?.VotosRecurridos ?? 0
            }
        };

        return Ok(respuestaDto);
    }

    [HttpGet("composicion-congreso")]
    public async Task<IActionResult> GetComposicionCongreso()
    {
        var config = await _dbContext.Configuraciones
            .AsNoTracking()
            .ToDictionaryAsync(c => c.Clave, c => c.Valor);

        // Aquí está el interruptor
        config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue);
        bool usarDatosOficiales = usarDatosOficialesValue == "true";

        if (usarDatosOficiales)
        {
            // Si el interruptor está en 'true', llama a este método
            return await GetComposicionDesdeBancadasOficiales(config);
        }
        else
        {
            // Si está en 'false' o no existe, llama a este otro
            return await GetComposicionDesdeProyecciones(config);
        }
    }

    // En ResultadosController.cs
    private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config)
    {
        config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue);
        bool mostrarOcupantes = mostrarOcupantesValue == "true";

        IQueryable<Bancada> bancadasQuery = _dbContext.Bancadas.AsNoTracking()
                                             .Include(b => b.AgrupacionPolitica);
        if (mostrarOcupantes)
        {
            bancadasQuery = bancadasQuery.Include(b => b.Ocupante);
        }
        var bancadas = await bancadasQuery.ToListAsync();

        var bancasPorAgrupacion = bancadas
            .Where(b => b.AgrupacionPolitica != null)
            // Agrupamos por el ID del partido, que es un valor único y estable
            .GroupBy(b => b.AgrupacionPolitica!.Id)
            .Select(g =>
            {
                // Tomamos la información de la agrupación del primer elemento (todas son iguales)
                var primeraBancaDelGrupo = g.First();
                var agrupacion = primeraBancaDelGrupo.AgrupacionPolitica!;

                // Filtramos los ocupantes solo para este grupo
                var ocupantesDelPartido = mostrarOcupantes
                    ? g.Select(b => b.Ocupante).Where(o => o != null).ToList()
                    : new List<OcupanteBanca?>();

                return new
                {
                    Agrupacion = agrupacion,
                    Camara = primeraBancaDelGrupo.Camara,
                    BancasTotales = g.Count(),
                    Ocupantes = ocupantesDelPartido
                };
            })
            .ToList();

        var presidenteDiputados = bancasPorAgrupacion
            .Where(b => b.Camara == Core.Enums.TipoCamara.Diputados)
            .OrderByDescending(b => b.BancasTotales)
            .FirstOrDefault()?.Agrupacion;

        config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores);
        var presidenteSenadores = await _dbContext.AgrupacionesPoliticas.FindAsync(idPartidoPresidenteSenadores);

        object MapearPartidos(Core.Enums.TipoCamara camara)
        {
            var partidosDeCamara = bancasPorAgrupacion.Where(b => b.Camara == camara);

            if (camara == Core.Enums.TipoCamara.Diputados)
                partidosDeCamara = partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenDiputados ?? 999);
            else
                partidosDeCamara = partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenSenadores ?? 999);

            return partidosDeCamara
                .OrderByDescending(p => p.BancasTotales)
                .Select(p => new
                {
                    p.Agrupacion.Id,
                    p.Agrupacion.Nombre,
                    p.Agrupacion.NombreCorto,
                    p.Agrupacion.Color,
                    p.BancasTotales,
                    p.Ocupantes // Pasamos la lista de ocupantes ya filtrada
                }).ToList();
        }

        var diputados = new
        {
            CamaraNombre = "Cámara de Diputados",
            TotalBancas = 92,
            BancasEnJuego = 0,
            Partidos = MapearPartidos(Core.Enums.TipoCamara.Diputados),
            PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null
        };

        var senadores = new
        {
            CamaraNombre = "Cámara de Senadores",
            TotalBancas = 46,
            BancasEnJuego = 0,
            Partidos = MapearPartidos(Core.Enums.TipoCamara.Senadores),
            PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null
        };

        return Ok(new { Diputados = diputados, Senadores = senadores });
    }

    private async Task<IActionResult> GetComposicionDesdeProyecciones(Dictionary<string, string> config)
    {
        var bancasPorAgrupacion = await _dbContext.ProyeccionesBancas
            .AsNoTracking()
            .GroupBy(p => new { p.AgrupacionPoliticaId, p.CategoriaId })
            .Select(g => new
            {
                AgrupacionId = g.Key.AgrupacionPoliticaId,
                CategoriaId = g.Key.CategoriaId,
                BancasTotales = g.Sum(p => p.NroBancas)
            })
            .ToListAsync();

        var todasAgrupaciones = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id);

        config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores);
        todasAgrupaciones.TryGetValue(idPartidoPresidenteSenadores ?? "", out var presidenteSenadores);

        string? idPartidoPresidenteDiputados = bancasPorAgrupacion
            .Where(b => b.CategoriaId == 6)
            .OrderByDescending(b => b.BancasTotales)
            .FirstOrDefault()?.AgrupacionId;
        todasAgrupaciones.TryGetValue(idPartidoPresidenteDiputados ?? "", out var presidenteDiputados);

        object MapearPartidos(int categoriaId)
        {
            var partidosDeCamara = bancasPorAgrupacion
                .Where(b => b.CategoriaId == categoriaId && b.BancasTotales > 0)
                .Select(b => new { Bancas = b, Agrupacion = todasAgrupaciones[b.AgrupacionId] });

            if (categoriaId == 6) // Diputados
                partidosDeCamara = partidosDeCamara.OrderBy(b => b.Agrupacion.OrdenDiputados ?? 999)
                                                   .ThenByDescending(b => b.Bancas.BancasTotales);
            else // Senadores
                partidosDeCamara = partidosDeCamara.OrderBy(b => b.Agrupacion.OrdenSenadores ?? 999)
                                                   .ThenByDescending(b => b.Bancas.BancasTotales);

            return partidosDeCamara
                    .Select(b => new
                    {
                        b.Agrupacion.Id,
                        b.Agrupacion.Nombre,
                        b.Agrupacion.NombreCorto,
                        b.Agrupacion.Color,
                        b.Bancas.BancasTotales,
                        Ocupantes = new List<object>() // <-- Siempre vacío en modo proyección
                    })
        .ToList();
        }

        var diputados = new
        {
            CamaraNombre = "Cámara de Diputados",
            TotalBancas = 92,
            BancasEnJuego = 46,
            Partidos = MapearPartidos(6),
            PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null
        };

        var senadores = new
        {
            CamaraNombre = "Cámara de Senadores",
            TotalBancas = 46,
            BancasEnJuego = 23,
            Partidos = MapearPartidos(5),
            PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null
        };

        return Ok(new { Diputados = diputados, Senadores = senadores });
    }

    [HttpGet("bancadas-detalle")]
    public async Task<IActionResult> GetBancadasConOcupantes()
    {
        var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor);

        config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue);
        if (usarDatosOficialesValue != "true")
        {
            // Si el modo oficial no está activo, SIEMPRE devolvemos un array vacío.
            return Ok(new List<object>());
        }

        // Si el modo oficial SÍ está activo, devolvemos los detalles.
        var bancadasConOcupantes = await _dbContext.Bancadas
            .AsNoTracking()
            .Include(b => b.Ocupante)
            .Select(b => new
            {
                b.Id,
                b.Camara,
                b.NumeroBanca,
                b.AgrupacionPoliticaId,
                Ocupante = b.Ocupante
            })
            .OrderBy(b => b.Id)
            .ToListAsync();

        return Ok(bancadasConOcupantes);
    }
    [HttpGet("configuracion-publica")]
    public async Task<IActionResult> GetConfiguracionPublica()
    {
        // Definimos una lista de las claves de configuración que son seguras para el público.
        // De esta manera, si en el futuro añadimos claves sensibles (como contraseñas de API, etc.),
        // nunca se expondrán accidentalmente.
        var clavesPublicas = new List<string>
    {
        "TickerResultadosCantidad",
        "ConcejalesResultadosCantidad"
        // "OtraClavePublica" 
        };

        var configuracionPublica = await _dbContext.Configuraciones
            .AsNoTracking()
            .Where(c => clavesPublicas.Contains(c.Clave))
            .ToDictionaryAsync(c => c.Clave, c => c.Valor);

        return Ok(configuracionPublica);
    }

    [HttpGet("seccion-resultados/{seccionId}")]
    public async Task<IActionResult> GetResultadosAgregadosPorSeccion(string seccionId, [FromQuery] int categoriaId)
    {
        var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos
            .AsNoTracking()
            .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId)
            .Select(a => a.Id)
            .ToListAsync();

        if (!municipiosDeLaSeccion.Any())
        {
            return Ok(new List<object>());
        }

        var resultadosMunicipales = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Include(r => r.AgrupacionPolitica)
            // Usamos la categoriaId del parámetro
            .Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId))
            .ToListAsync();

        var logos = await _dbContext.LogosAgrupacionesCategorias
            .AsNoTracking()
            // Usamos la categoriaId del parámetro
            .Where(l => l.CategoriaId == categoriaId)
            .ToDictionaryAsync(l => l.AgrupacionPoliticaId);

        var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos);

        var resultadosFinales = resultadosMunicipales
            .GroupBy(r => r.AgrupacionPoliticaId)
            .Select(g => new
            {
                Agrupacion = g.First().AgrupacionPolitica,
                Votos = g.Sum(r => r.CantidadVotos)
            })
            .OrderByDescending(r => r.Votos)
            .Select(r => new
            {
                Id = r.Agrupacion.Id,
                r.Agrupacion.Nombre,
                r.Agrupacion.NombreCorto,
                r.Agrupacion.Color,
                LogoUrl = logos.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl,
                Votos = r.Votos,
                Porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
            })
            .ToList();

        // Devolvemos un objeto para poder añadir la fecha de actualización
        var seccionAmbito = await _dbContext.AmbitosGeograficos.AsNoTracking()
            .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20);
        var estadoRecuento = seccionAmbito != null
            ? await _dbContext.EstadosRecuentos.AsNoTracking()
                .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == seccionAmbito.Id && e.CategoriaId == categoriaId)
            : null;

        return Ok(new
        {
            UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow,
            Resultados = resultadosFinales
        });
    }

    [HttpGet("mapa-por-seccion")]
    public async Task<IActionResult> GetResultadosMapaPorSeccion([FromQuery] int categoriaId)
    {
        // 1. Obtenemos todos los resultados a nivel de MUNICIPIO para la categoría dada.
        var resultadosMunicipales = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Include(r => r.AmbitoGeografico)
            .Include(r => r.AgrupacionPolitica)
            .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30)
            .ToListAsync();

        // 2. Agrupamos en memoria por Sección Electoral y sumamos los votos.
        var ganadoresPorSeccion = resultadosMunicipales
            .GroupBy(r => r.AmbitoGeografico.SeccionProvincialId)
            .Select(g =>
            {
                // Para cada sección, encontramos al partido con más votos.
                var ganador = g
                    .GroupBy(r => r.AgrupacionPolitica)
                    .Select(pg => new { Agrupacion = pg.Key, TotalVotos = pg.Sum(r => r.CantidadVotos) })
                    .OrderByDescending(x => x.TotalVotos)
                    .FirstOrDefault();

                // Buscamos el nombre de la sección
                var seccionInfo = _dbContext.AmbitosGeograficos
                    .FirstOrDefault(a => a.SeccionProvincialId == g.Key && a.NivelId == 20);

                return new
                {
                    SeccionId = g.Key,
                    SeccionNombre = seccionInfo?.Nombre,
                    AgrupacionGanadoraId = ganador?.Agrupacion.Id,
                    ColorGanador = ganador?.Agrupacion.Color
                };
            })
            .Where(r => r.SeccionId != null) // Filtramos cualquier posible nulo
            .ToList();

        return Ok(ganadoresPorSeccion);
    }

    [HttpGet("seccion/{seccionId}")]
    public async Task<IActionResult> GetResultadosDetallePorSeccion(string seccionId, [FromQuery] int categoriaId)
    {
        var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos
            .AsNoTracking()
            .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId)
            .Select(a => a.Id)
            .ToListAsync();

        if (!municipiosDeLaSeccion.Any()) return Ok(new List<object>());

        var resultadosMunicipales = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Include(r => r.AgrupacionPolitica)
            .Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId))
            .ToListAsync();

        var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos);

        var resultadosFinales = resultadosMunicipales
            // 1. Agrupamos por el ID del partido para evitar duplicados.
            .GroupBy(r => r.AgrupacionPoliticaId)
            .Select(g => new
            {
                // 2. Tomamos la entidad completa del primer elemento del grupo.
                Agrupacion = g.First().AgrupacionPolitica,
                Votos = g.Sum(r => r.CantidadVotos)
            })
            .OrderByDescending(r => r.Votos)
            .Select(r => new
            {
                id = r.Agrupacion.Id,
                nombre = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre,
                votos = r.Votos,
                porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0,
                // 3. Añadimos el color a la respuesta.
                color = r.Agrupacion.Color
            })
            .ToList();

        return Ok(resultadosFinales);
    }

    [HttpGet("mapa-por-municipio")]
    public async Task<IActionResult> GetResultadosMapaPorMunicipio([FromQuery] int categoriaId)
    {
        // Obtenemos los votos primero
        var votosPorMunicipio = await _dbContext.ResultadosVotos
            .AsNoTracking()
            .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30)
            .ToListAsync();

        // Luego, los agrupamos en memoria
        var ganadores = votosPorMunicipio
            .GroupBy(r => r.AmbitoGeograficoId)
            .Select(g => g.OrderByDescending(r => r.CantidadVotos).First())
            .ToList();

        // Ahora, obtenemos los detalles necesarios en una sola consulta adicional
        var idsAgrupacionesGanadoras = ganadores.Select(g => g.AgrupacionPoliticaId).ToList();
        var idsAmbitosGanadores = ganadores.Select(g => g.AmbitoGeograficoId).ToList();

        var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas
            .AsNoTracking()
            .Where(a => idsAgrupacionesGanadoras.Contains(a.Id))
            .ToDictionaryAsync(a => a.Id);

        var ambitosInfo = await _dbContext.AmbitosGeograficos
            .AsNoTracking()
            .Where(a => idsAmbitosGanadores.Contains(a.Id))
            .ToDictionaryAsync(a => a.Id);

        // Finalmente, unimos todo en memoria
        var resultadoFinal = ganadores.Select(g => new
        {
            AmbitoId = g.AmbitoGeograficoId,
            DepartamentoNombre = ambitosInfo.GetValueOrDefault(g.AmbitoGeograficoId)?.Nombre,
            AgrupacionGanadoraId = g.AgrupacionPoliticaId,
            ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color
        })
        .Where(r => r.DepartamentoNombre != null) // Filtramos por si acaso
        .ToList();

        return Ok(resultadoFinal);
    }
}