//Elecciones.Worker/Program.cs
using Elecciones.Database;
using Elecciones.Infrastructure.Services;
using Elecciones.Worker;
using Microsoft.EntityFrameworkCore;
using System.Net;
using Serilog;
using System.Net.Security;
using System.Security.Authentication;
using Polly;
using Polly.Extensions.Http;
using System.Threading.RateLimiting;

var builder = Host.CreateApplicationBuilder(args);

// 1. Registra el servicio del interruptor.
builder.Services.AddSingleton<LoggingSwitchService>();

// 2. Configura Serilog usando UseSerilog en el HostBuilder.
//    (Nota: AddSerilog también funciona, pero UseSerilog es más consistente con la API)
builder.Services.AddSerilog((services, configuration) => {
    var loggingSwitch = services.GetRequiredService<LoggingSwitchService>();
    configuration
        .ReadFrom.Configuration(builder.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        .MinimumLevel.ControlledBy(loggingSwitch.LevelSwitch)
        .WriteTo.Console()
        .WriteTo.File("logs/worker-.log", rollingInterval: RollingInterval.Day);
});

// --- Configuración de Servicios ---
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<EleccionesDbContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddHttpClient("ElectoralApiClient", client =>
{
    var baseUrl = builder.Configuration["ElectoralApi:BaseUrl"];
    if (!string.IsNullOrEmpty(baseUrl))
    {
        client.BaseAddress = new Uri(baseUrl);
    }

    // --- TIMEOUT MÁS LARGO ---
    // Aumentamos el tiempo de espera a 90 segundos.
    // Esto le dará a las peticiones lentas de la API tiempo suficiente para responder.
    client.Timeout = TimeSpan.FromSeconds(90);

    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
    client.DefaultRequestHeaders.Add("Accept", "*/*");
    client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
    client.DefaultRequestHeaders.Add("Connection", "keep-alive");

})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new SocketsHttpHandler
    {
        // Habilita la descompresión automática de respuestas GZIP y Deflate.
        // Esto soluciona el error de JsonException ('0x1F').
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,

        SslOptions = new SslClientAuthenticationOptions
        {
            EnabledSslProtocols = SslProtocols.Tls13,
        }
    };

    if (!OperatingSystem.IsWindows())
    {
        handler.SslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(new[]
        {
            TlsCipherSuite.TLS_AES_128_GCM_SHA256,
            TlsCipherSuite.TLS_AES_256_GCM_SHA384,
            TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256
        });
    }

    return handler;
})

.AddPolicyHandler(GetRetryPolicy());

// --- LIMITADOR DE VELOCIDAD BASADO EN TOKEN BUCKET ---
builder.Services.AddSingleton<RateLimiter>(sp => 
    new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
    {
        TokenLimit = 50, // Ráfaga máxima permitida
        ReplenishmentPeriod = TimeSpan.FromSeconds(1),
        TokensPerPeriod = 20, // Ritmo de recarga: 20 peticiones por segundo
        QueueLimit = 1000,
        QueueProcessingOrder = QueueProcessingOrder.OldestFirst
    }));
    
builder.Services.AddScoped<IElectoralApiService, ElectoralApiService>();

// Registramos el servicio de token como un Singleton para que sea compartido.
builder.Services.AddSingleton<SharedTokenService>();
// Registramos el servicio de configuraciones de workers como un Singleton para que sea compartido.
builder.Services.AddSingleton<WorkerConfigService>();

// Registramos ambos workers. El framework se encargará de iniciarlos y detenerlos.
builder.Services.AddHostedService<CriticalDataWorker>();
builder.Services.AddHostedService<LowPriorityDataWorker>();

var host = builder.Build();

// La lógica para leer el nivel de logging se ejecuta DESPUÉS de construir el host.
using (var scope = host.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var dbContext = services.GetRequiredService<EleccionesDbContext>();
        var loggingSwitchService = services.GetRequiredService<LoggingSwitchService>();
        
        var logLevelConfig = await dbContext.Configuraciones
            .AsNoTracking()
            .FirstOrDefaultAsync(c => c.Clave == "Logging_Level");

        if (logLevelConfig != null)
        {
            loggingSwitchService.SetLoggingLevel(logLevelConfig.Valor);
            // Obtenemos un logger de la forma correcta para registrar este evento
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogInformation("Nivel de logging inicial establecido desde la BD a: {Level}", logLevelConfig.Valor);
        }
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "No se pudo establecer el nivel de logging desde la BD. Usando el nivel por defecto.");
    }
}

try
{
    // Usamos el logger del host para el mensaje de inicio
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("Iniciando Elecciones.Worker Host...");
    host.Run();
}
catch (Exception ex)
{
    // En caso de un error fatal, el logger estático (si está configurado) puede ser un respaldo
    Log.Fatal(ex, "El Host de Elecciones.Worker terminó inesperadamente");
}
finally
{
    Log.CloseAndFlush();
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        // Manejar peticiones que fallaron por errores de red O que devolvieron un error de servidor (como 504)
        .HandleTransientHttpError()
        // O que devolvieron un código 504 Gateway Timeout específicamente
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.GatewayTimeout)
        // Esperar y reintentar. La espera se duplica en cada reintento.
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 
            onRetry: (outcome, timespan, retryAttempt, context) =>
            {
                // Opcional: Loguear cada reintento. Necesitarías pasar ILogger aquí.
                // Log.Warning("Retrying due to {StatusCode}. Waited {seconds}s. Attempt {retryAttempt}/3", outcome.Result.StatusCode, timespan.TotalSeconds, retryAttempt);
            });
}