// src/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;
  private static readonly ConcurrentDictionary<string, DateTime> _lastUpdateTracker = new();

  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 (Sondeo Inteligente) iniciado.");
    await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);

    while (!stoppingToken.IsCancellationRequested)
    {
      var settings = await _configService.GetSettingsAsync();
      if (!settings.ResultadosActivado)
      {
        _logger.LogInformation("El sondeo de resultados está desactivado. Esperando 1 minuto.");
        await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        continue;
      }

      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;
      }

      _logger.LogInformation("--- Iniciando Ciclo de Sondeo Inteligente ---");
      await SondearEstadosYDispararActualizaciones(authToken, stoppingToken);
      _logger.LogInformation("--- Ciclo de Sondeo Inteligente completado. ---");

      try
      {
        await Task.Delay(TimeSpan.FromSeconds(45), stoppingToken);
      }
      catch (TaskCanceledException) { break; }
    }
  }

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

      var ambitosProvinciales = await dbContext.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10 && a.DistritoId != null).ToListAsync(stoppingToken);
      var ambitoNacional = await dbContext.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
      var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);

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

      // --- PASO 1: SONDEO PROVINCIAL ---
      foreach (var provincia in ambitosProvinciales)
      {
        if (stoppingToken.IsCancellationRequested) break;
        foreach (var categoria in categorias)
        {
          if (stoppingToken.IsCancellationRequested) break;

          var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
          if (resultadosDto == null) continue;

          // --- Guardar los datos agregados inmediatamente ---
          await GuardarDatosAgregadosAsync(dbContext, provincia.Id, categoria.Id, resultadosDto, stoppingToken);

          if (DateTime.TryParse(resultadosDto.FechaTotalizacion, out var fechaApi))
          {
            var trackerKey = $"{provincia.Id}-{categoria.Id}";
            _lastUpdateTracker.TryGetValue(trackerKey, out var ultimaFechaConocida);

            if (fechaApi > ultimaFechaConocida)
            {
              _logger.LogInformation("¡Cambio detectado para {Provincia} - {Categoria}! Actualizando detalles en segundo plano...", provincia.Nombre, categoria.Nombre);
              _lastUpdateTracker[trackerKey] = fechaApi;
              _ = Task.Run(() => ActualizarResultadosDeProvincia(authToken, provincia.DistritoId!, categoria.Id), stoppingToken);
            }
          }
        }
      }

      // --- PASO 2: SONDEO NACIONAL ---
      if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
      {
        _logger.LogInformation("Sondeando totales a nivel Nacional...");
        foreach (var categoria in categorias)
        {
          if (stoppingToken.IsCancellationRequested) break;
          var resultadosNacionalDto = await _apiService.GetResultadosAsync(authToken, "", null, null, categoria.Id);
          if (resultadosNacionalDto != null)
          {
            await GuardarDatosAgregadosAsync(dbContext, ambitoNacional.Id, categoria.Id, resultadosNacionalDto, stoppingToken);
          }
        }
      }
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Error en el ciclo de sondeo de estados.");
    }
  }

  // --- FUNCIÓN AUXILIAR PARA GUARDAR DATOS AGREGADOS ---
  private async Task GuardarDatosAgregadosAsync(EleccionesDbContext dbContext, int ambitoId, int categoriaId, Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
  {
    await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);

    // 1. Actualizar EstadoRecuentoGeneral
    var estadoGeneral = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
    if (estadoGeneral == null)
    {
      estadoGeneral = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId };
      dbContext.EstadosRecuentosGenerales.Add(estadoGeneral);
    }
    estadoGeneral.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion);
    estadoGeneral.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
    estadoGeneral.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
    estadoGeneral.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
    estadoGeneral.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores;
    estadoGeneral.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes;
    estadoGeneral.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje;

    // 2. Actualizar ResumenesVotos
    if (resultadosDto.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
    {
      await dbContext.ResumenesVotos
          .Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId)
          .ExecuteDeleteAsync(stoppingToken);

      foreach (var voto in nuevosVotos)
      {
        dbContext.ResumenesVotos.Add(new ResumenVoto
        {
          EleccionId = EleccionId,
          AmbitoGeograficoId = ambitoId,
          CategoriaId = categoriaId,
          AgrupacionPoliticaId = voto.IdAgrupacion,
          Votos = voto.Votos,
          VotosPorcentaje = voto.VotosPorcentaje
        });
      }
    }

    await dbContext.SaveChangesAsync(stoppingToken);
    await transaction.CommitAsync(stoppingToken);
  }

  private async Task ActualizarResultadosDeProvincia(string authToken, string distritoId, int categoriaId)
  {
    try
    {
      using var scope = _serviceProvider.CreateScope();
      var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();

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

      _logger.LogInformation("Iniciando descarga detallada para {Count} municipios del distrito {DistritoId}...", municipiosDeLaProvincia.Count, distritoId);

      foreach (var municipio in municipiosDeLaProvincia)
      {
        var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoriaId);
        if (resultados != null)
        {
          await GuardarResultadosDeAmbitoAsync(dbContext, municipio.Id, categoriaId, resultados, CancellationToken.None);
        }
      }

      _logger.LogInformation("Descarga detallada para el distrito {DistritoId} completada.", distritoId);
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, "Error en la tarea de actualización de resultados para el distrito {DistritoId}.", distritoId);
    }
  }

  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);
    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);
    }
  }
}