//Elecciones.Worker/CriticalDataWorker.cs
using Elecciones.Database;
using Elecciones.Database.Entities;
using Elecciones.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;

namespace Elecciones.Worker;

public class CriticalDataWorker : BackgroundService
{
  private const int EleccionId = 2;
  private readonly ILogger<CriticalDataWorker> _logger;
  private readonly SharedTokenService _tokenService;
  private readonly IServiceProvider _serviceProvider;
  private readonly IElectoralApiService _apiService;
  private readonly WorkerConfigService _configService;

  public CriticalDataWorker(
      ILogger<CriticalDataWorker> logger,
      SharedTokenService tokenService,
      IServiceProvider serviceProvider,
      IElectoralApiService apiService,
      WorkerConfigService configService)
  {
    _logger = logger;
    _tokenService = tokenService;
    _serviceProvider = serviceProvider;
    _apiService = apiService;
    _configService = configService;
  }

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    _logger.LogInformation("Worker de Datos Críticos iniciado.");

    try
    {
      await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
    }
    catch (TaskCanceledException) { return; }

    int cicloContador = 0;
    while (!stoppingToken.IsCancellationRequested)
    {
      var cicloInicio = DateTime.UtcNow;
      cicloContador++;
      _logger.LogInformation("--- Iniciando Ciclo de Datos Críticos #{ciclo} ---", cicloContador);

      var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
      if (string.IsNullOrEmpty(authToken))
      {
        _logger.LogError("Ciclo Crítico: No se pudo obtener token. Reintentando en 30 segundos.");
        await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        continue;
      }

      var settings = await _configService.GetSettingsAsync();

      if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
      {
        _logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
        await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
        await SondearResumenProvincialAsync(authToken, stoppingToken);
        await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
      }
      else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
      {
        _logger.LogInformation("Ejecutando tareas de Baja Prioridad en alta prioridad.");
        await SondearProyeccionBancasAsync(authToken, stoppingToken);
        //await SondearNuevosTelegramasAsync(authToken, stoppingToken);
      }
      else
      {
        _logger.LogInformation("Worker de alta prioridad inactivo según la configuración.");
      }

      var cicloFin = DateTime.UtcNow;
      var duracionCiclo = cicloFin - cicloInicio;
      _logger.LogInformation("--- Ciclo de Datos Críticos #{ciclo} completado en {duration:N2} segundos. ---", cicloContador, duracionCiclo.TotalSeconds);

      var tiempoDeEspera = TimeSpan.FromSeconds(30) - duracionCiclo;
      if (tiempoDeEspera < TimeSpan.Zero) tiempoDeEspera = TimeSpan.Zero;

      try
      {
        await Task.Delay(tiempoDeEspera, stoppingToken);
      }
      catch (TaskCanceledException) { break; }
    }
  }

  /// <summary>
  /// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
  /// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
  /// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos.
  /// </summary>
  /// <param name="authToken">El token de autenticación válido para la sesión.</param>
  /// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
  private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken)
  {
    try
    {
      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

      var categoriasDeBancas = await dbContext.CategoriasElectorales
          .AsNoTracking()
          .Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS"))
          .ToListAsync(stoppingToken);

      // --- MODIFICACIÓN 1: Obtener todos los ámbitos en una sola consulta ---
      var ambitosASondear = await dbContext.AmbitosGeograficos
          .AsNoTracking()
          .Where(a => (a.NivelId == 10 || a.NivelId == 20) && a.DistritoId != null)
          .ToListAsync(stoppingToken);

      var provincia = ambitosASondear.FirstOrDefault(a => a.NivelId == 10);
      var seccionesElectorales = ambitosASondear.Where(a => a.NivelId == 20).ToList();

      if (!categoriasDeBancas.Any() || provincia == null)
      {
        _logger.LogWarning("No se encontraron categorías de bancas o el ámbito provincial en la BD. Omitiendo sondeo de bancas.");
        return;
      }

      _logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial y para {count} Secciones Electorales...", seccionesElectorales.Count);

      var todasLasProyecciones = new List<ProyeccionBanca>();
      bool hasReceivedAnyNewData = false;

      // --- MODIFICACIÓN 2: Usar un diccionario para no buscar repetidamente en la BD ---
      var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken);

      // Bucle combinado para todos los ámbitos
      foreach (var ambito in ambitosASondear)
      {
        if (stoppingToken.IsCancellationRequested) break;

        foreach (var categoria in categoriasDeBancas)
        {
          if (stoppingToken.IsCancellationRequested) break;

          // Llamada a la API (lógica adaptada para ambos niveles)
          var repartoBancasDto = await _apiService.GetBancasAsync(authToken, ambito.DistritoId!, ambito.SeccionProvincialId, categoria.Id);

          if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas)
          {
            hasReceivedAnyNewData = true;

            DateTime fechaTotalizacion;
            if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate))
            {
              _logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas. Usando la hora actual.", repartoBancasDto.FechaTotalizacion);
              fechaTotalizacion = DateTime.UtcNow;
            }
            else
            {
              fechaTotalizacion = parsedDate.ToUniversalTime();
            }

            foreach (var banca in bancas)
            {
              // --- MODIFICACIÓN 3: Lógica de "Upsert" para Agrupaciones ---
              if (!agrupacionesEnDb.ContainsKey(banca.IdAgrupacion))
              {
                _logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada. Creándola desde los datos de bancas.", banca.IdAgrupacion, banca.NombreAgrupacion);

                var nuevaAgrupacion = new AgrupacionPolitica
                {
                  Id = banca.IdAgrupacion,
                  Nombre = banca.NombreAgrupacion,
                  IdTelegrama = banca.IdAgrupacionTelegrama ?? string.Empty
                };

                await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken);
                agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion); // Añadir al diccionario para no volver a crearla
              }

              todasLasProyecciones.Add(new ProyeccionBanca
              {
                EleccionId = EleccionId,
                AmbitoGeograficoId = ambito.Id,
                AgrupacionPoliticaId = banca.IdAgrupacion,
                NroBancas = banca.NroBancas,
                CategoriaId = categoria.Id,
                FechaTotalizacion = fechaTotalizacion
              });
            }
          }
        }
      }

      if (hasReceivedAnyNewData)
      {
        _logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos...");
        await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);

        // Si se crearon nuevas agrupaciones, se guardarán aquí primero.
        await dbContext.SaveChangesAsync(stoppingToken);

        // Luego, procedemos con las proyecciones.
        await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken);
        await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken);
        await dbContext.SaveChangesAsync(stoppingToken);
        await transaction.CommitAsync(stoppingToken);

        _logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count);
      }
      else
      {
        _logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos, la tabla no fue modificada.");
      }
    }
    catch (OperationCanceledException)
    {
      _logger.LogInformation("Sondeo de bancas cancelado.");
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas.");
    }
  }

  /// <summary>
  /// Busca y descarga nuevos telegramas de forma masiva y concurrente.
  /// Este método crea una lista de todas las combinaciones de Partido/Categoría,
  /// las consulta a la API con un grado de paralelismo controlado, y cada tarea concurrente
  /// maneja su propia lógica de descarga y guardado en la base de datos.
  /// </summary>
  /// <param name="authToken">El token de autenticación válido para la sesión.</param>
  /// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
  private async Task SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken)
  {
    try
    {
      _logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas (modo de bajo perfil) ---");

      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

      // La obtención de partidos y categorías no cambia
      var partidos = await dbContext.AmbitosGeograficos.AsNoTracking()
          .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
          .ToListAsync(stoppingToken);
      var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);

      if (!partidos.Any() || !categorias.Any()) return;

      foreach (var partido in partidos)
      {
        foreach (var categoria in categorias)
        {
          if (stoppingToken.IsCancellationRequested) return;

          var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id);

          if (listaTelegramasApi is { Count: > 0 })
          {
            // Creamos el DbContext para la operación de guardado
            using var innerScope = _serviceProvider.CreateScope();
            var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

            var idsYaEnDb = await innerDbContext.Telegramas
                .Where(t => listaTelegramasApi.Contains(t.Id))
                .Select(t => t.Id).ToListAsync(stoppingToken);

            var nuevosTelegramasIds = listaTelegramasApi.Except(idsYaEnDb).ToList();

            if (nuevosTelegramasIds.Any())
            {
              _logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre);

              var originalTimeout = innerDbContext.Database.GetCommandTimeout();
              try
              {
                innerDbContext.Database.SetCommandTimeout(180);
                _logger.LogDebug("Timeout de BD aumentado a 180s para descarga de telegramas.");

                int contadorLote = 0;
                const int tamanoLote = 100;

                foreach (var mesaId in nuevosTelegramasIds)
                {
                  if (stoppingToken.IsCancellationRequested) return;

                  var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId);
                  if (telegramaFile != null)
                  {
                    var ambitoMesa = await innerDbContext.AmbitosGeograficos.AsNoTracking()
                        .FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken);

                    if (ambitoMesa != null)
                    {
                      var nuevoTelegrama = new Telegrama
                      {
                        EleccionId = EleccionId,
                        Id = telegramaFile.NombreArchivo,
                        AmbitoGeograficoId = ambitoMesa.Id,
                        ContenidoBase64 = telegramaFile.Imagen,
                        FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
                        FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
                      };
                      await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
                      contadorLote++;
                    }
                    else
                    {
                      _logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId);
                    }
                  }
                  await Task.Delay(250, stoppingToken);

                  if (contadorLote >= tamanoLote)
                  {
                    await innerDbContext.SaveChangesAsync(stoppingToken);
                    _logger.LogInformation("Guardado un lote de {count} telegramas.", contadorLote);
                    contadorLote = 0;
                  }
                }

                if (contadorLote > 0)
                {
                  await innerDbContext.SaveChangesAsync(stoppingToken);
                  _logger.LogInformation("Guardado el último lote de {count} telegramas.", contadorLote);
                }
              }
              finally
              {
                innerDbContext.Database.SetCommandTimeout(originalTimeout);
                _logger.LogDebug("Timeout de BD restaurado a su valor original ({timeout}s).", originalTimeout);
              }
            } // Fin del if (nuevosTelegramasIds.Any())

            // Movemos el delay aquí para que solo se ejecute si hubo telegramas en la respuesta de la API
            await Task.Delay(100, stoppingToken);
          } // Fin del if (listaTelegramasApi is not null)
        }
      }
      _logger.LogInformation("Sondeo de Telegramas completado.");
    }
    catch (OperationCanceledException)
    {
      _logger.LogInformation("Sondeo de telegramas cancelado.");
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas.");
    }
  }

  private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken)
  {
    try
    {
      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

      var municipiosASondear = await dbContext.AmbitosGeograficos
          .AsNoTracking()
          .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
          .ToListAsync(stoppingToken);

      var todasLasCategorias = await dbContext.CategoriasElectorales
          .AsNoTracking()
          .ToListAsync(stoppingToken);

      if (!municipiosASondear.Any() || !todasLasCategorias.Any())
      {
        _logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados.");
        return;
      }

      _logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count);

      foreach (var municipio in municipiosASondear)
      {
        if (stoppingToken.IsCancellationRequested) break;

        var tareasCategoria = todasLasCategorias.Select(async categoria =>
        {
          var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id);

          if (resultados != null)
          {
            using var innerScope = _serviceProvider.CreateScope();
            var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

            // --- LLAMADA CORRECTA ---
            await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken);
          }
        });

        await Task.WhenAll(tareasCategoria);
      }
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales.");
    }
  }

  private async Task GuardarResultadosDeAmbitoAsync(
      EleccionesDbContext dbContext, int ambitoId, int categoriaId,
      Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
  {
    var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);

    if (estadoRecuento == null)
    {
      estadoRecuento = new EstadoRecuento { EleccionId = EleccionId, AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId };
      dbContext.EstadosRecuentos.Add(estadoRecuento);
    }

    estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime();
    estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
    estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
    estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
    estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores;
    estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes;
    estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje;

    if (resultadosDto.ValoresTotalizadosOtros != null)
    {
      estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco;
      estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje;
      estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos;
      estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje;
      estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos;
      estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje;
      estadoRecuento.VotosComando = resultadosDto.ValoresTotalizadosOtros.VotosComando;
      estadoRecuento.VotosComandoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosComandoPorcentaje;
      estadoRecuento.VotosImpugnados = resultadosDto.ValoresTotalizadosOtros.VotosImpugnados;
      estadoRecuento.VotosImpugnadosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosImpugnadosPorcentaje;
    }

    foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos)
    {
      // PASO 1: VERIFICAR SI LA AGRUPACIÓN YA EXISTE EN NUESTRA BD
      var agrupacion = await dbContext.AgrupacionesPoliticas.FindAsync(votoPositivoDto.IdAgrupacion);

      // PASO 2: SI NO EXISTE, LA CREAMOS "SOBRE LA MARCHA"
      if (agrupacion == null)
      {
        _logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada en el catálogo local. Creándola desde los datos de resultados.",
            votoPositivoDto.IdAgrupacion, votoPositivoDto.NombreAgrupacion);

        agrupacion = new AgrupacionPolitica
        {
          Id = votoPositivoDto.IdAgrupacion,
          Nombre = votoPositivoDto.NombreAgrupacion,
          // El IdTelegrama puede ser nulo, usamos el operador '??' para asignar un string vacío si es así.
          IdTelegrama = votoPositivoDto.IdAgrupacionTelegrama ?? string.Empty
        };
        await dbContext.AgrupacionesPoliticas.AddAsync(agrupacion, stoppingToken);
        // No es necesario llamar a SaveChangesAsync aquí, se hará al final.
      }

      // PASO 3: CONTINUAR CON LA LÓGICA DE GUARDADO DEL VOTO
      var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync(
          rv => rv.AmbitoGeograficoId == ambitoId &&
                rv.CategoriaId == categoriaId &&
                rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion,
          stoppingToken
      );

      if (resultadoVoto == null)
      {
        resultadoVoto = new ResultadoVoto
        {
          EleccionId = EleccionId,
          AmbitoGeograficoId = ambitoId,
          CategoriaId = categoriaId,
          AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion
        };
        dbContext.ResultadosVotos.Add(resultadoVoto);
      }
      resultadoVoto.CantidadVotos = votoPositivoDto.Votos;
      resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje;
    }

    try
    {
      await dbContext.SaveChangesAsync(stoppingToken);
    }
    catch (DbUpdateException ex)
    {
      _logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId);
    }
  }

  /// <summary>
  /// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
  /// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
  /// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
  /// </summary>
  private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
  {
    try
    {
      _logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");

      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

      var provinciasASondear = await dbContext.AmbitosGeograficos
          .AsNoTracking()
          .Where(a => a.NivelId == 10 && a.DistritoId != null)
          .ToListAsync(stoppingToken);

      var todasLasCategorias = await dbContext.CategoriasElectorales
          .AsNoTracking()
          .ToListAsync(stoppingToken);

      if (!provinciasASondear.Any() || !todasLasCategorias.Any())
      {
        _logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
        return;
      }

      foreach (var provincia in provinciasASondear)
      {
        if (stoppingToken.IsCancellationRequested) break;

        foreach (var categoria in todasLasCategorias)
        {
          if (stoppingToken.IsCancellationRequested) break;

          // Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
          var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);

          if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
          {
            // Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
            await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);

            // A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
            await dbContext.ResumenesVotos
                .Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
                .ExecuteDeleteAsync(stoppingToken);

            // B. Añadir los nuevos resúmenes.
            foreach (var voto in nuevosVotos)
            {
              dbContext.ResumenesVotos.Add(new ResumenVoto
              {
                EleccionId = EleccionId,
                AmbitoGeograficoId = provincia.Id,
                CategoriaId = categoria.Id,
                AgrupacionPoliticaId = voto.IdAgrupacion,
                Votos = voto.Votos,
                VotosPorcentaje = voto.VotosPorcentaje
              });
            }

            // C. Guardar los cambios en la tabla ResumenesVotos.
            await dbContext.SaveChangesAsync(stoppingToken);

            // No es necesario actualizar EstadosRecuentosGenerales aquí,
            // ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
            // de forma más específica y eficiente.

            await transaction.CommitAsync(stoppingToken);
          }
        } // Fin bucle categorías
      } // Fin bucle provincias

      _logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
    }
    catch (OperationCanceledException)
    {
      _logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resúmenes Provinciales.");
    }
  }

  /// <summary>
  /// Obtiene y actualiza el estado general del recuento a nivel provincial para CADA categoría electoral.
  /// Esta versión es robusta: consulta dinámicamente las categorías, usa la clave primaria compuesta
  /// de la base de datos y guarda todos los cambios en una única transacción al final.
  /// </summary>
  /// <param name="authToken">El token de autenticación válido para la sesión.</param>
  /// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
  private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken)
  {
    try
    {
      // PASO 1: Crear un "scope" para obtener una instancia fresca de DbContext.
      // Esto es una práctica recomendada para servicios de larga duración para evitar problemas de concurrencia.
      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

      // PASO 2: Obtener el ámbito geográfico de la Provincia.
      // Necesitamos este objeto para obtener su 'DistritoId' ("02"), que es requerido por la API.
      var provincia = await dbContext.AmbitosGeograficos
          .AsNoTracking() // Optimización: Solo necesitamos leer datos, no modificarlos.
          .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken);

      // Comprobación de seguridad: Si la sincronización inicial falló y no tenemos el registro de la provincia,
      // no podemos continuar. Registramos una advertencia y salimos del método.
      if (provincia == null)
      {
        _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general.");
        return;
      }

      // PASO 3: Obtener todas las categorías electorales disponibles desde nuestra base de datos.
      // Esto hace que el método sea dinámico y no dependa de IDs fijos en el código.
      var categoriasParaSondear = await dbContext.CategoriasElectorales
          .AsNoTracking()
          .ToListAsync(stoppingToken);

      if (!categoriasParaSondear.Any())
      {
        _logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento.");
        return;
      }

      _logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count);

      // PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual.
      foreach (var categoria in categoriasParaSondear)
      {
        // Salimos limpiamente del bucle si la aplicación se está deteniendo.
        if (stoppingToken.IsCancellationRequested) break;

        // Llamamos a la API con el distrito y la CATEGORÍA ACTUAL del bucle.
        var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);

        // Solo procedemos si la API devolvió datos válidos.
        if (estadoDto != null)
        {
          // Lógica "Upsert" (Update or Insert):
          // Buscamos un registro existente usando la CLAVE PRIMARIA COMPUESTA.
          var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(
              new object[] { provincia.Id, categoria.Id },
              cancellationToken: stoppingToken
          );

          // Si no se encuentra (FindAsync devuelve null), es un registro nuevo.
          if (registroDb == null)
          {
            // Creamos una nueva instancia de la entidad.
            registroDb = new EstadoRecuentoGeneral
            {
              EleccionId = EleccionId,
              AmbitoGeograficoId = provincia.Id,
              CategoriaId = categoria.Id // Asignamos ambas partes de la clave primaria.
            };
            // Y la añadimos al ChangeTracker de EF para que la inserte en la BD.
            dbContext.EstadosRecuentosGenerales.Add(registroDb);
          }

          // Mapeamos los datos del DTO de la API a nuestra entidad de base de datos.
          // Esto se hace tanto para registros nuevos como para los existentes que se van a actualizar.
          registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
          registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
          registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
          registroDb.CantidadElectores = estadoDto.CantidadElectores;
          registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
          registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
        }
      }

      // PASO 5: Guardar todos los cambios en la base de datos.
      // Al llamar a SaveChangesAsync UNA SOLA VEZ fuera del bucle, EF Core agrupa
      // todas las inserciones y actualizaciones en una única transacción eficiente.
      await dbContext.SaveChangesAsync(stoppingToken);
      _logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías.");
    }
    catch (Exception ex)
    {
      // Capturamos cualquier excepción inesperada para que no detenga el worker y la registramos.
      _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
    }
  }
}
