//Elecciones.Api/Program.cs
using Elecciones.Database;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Elecciones.Core.Services;
using Elecciones.Infrastructure.Services;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Elecciones.Database.Entities;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.HttpOverrides;

// Esta es la estructura estándar y recomendada.
var builder = WebApplication.CreateBuilder(args);

// 1. Registra el servicio del interruptor como un Singleton.
//    Esto asegura que toda la aplicación comparta la MISMA instancia del interruptor.
builder.Services.AddSingleton<LoggingSwitchService>();

builder.Host.UseSerilog((context, services, configuration) =>
{
    // 2. Obtenemos la instancia del interruptor que acabamos de registrar.
    var loggingSwitch = services.GetRequiredService<LoggingSwitchService>();

    configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        // 3. Establecemos el nivel mínimo de logging controlado por el interruptor.
        .MinimumLevel.ControlledBy(loggingSwitch.LevelSwitch)
        .WriteTo.Console()
        .WriteTo.File("logs/api-.log", rollingInterval: RollingInterval.Day); // o "logs/worker-.log"
});

// 2. Añadir servicios al contenedor.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<EleccionesDbContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddScoped<IPasswordHasher, PasswordHasher>();

builder.Services.AddControllers().AddJsonOptions(options =>
{
    // Esto le dice al serializador que maneje las referencias circulares
    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins(
                              "http://localhost:5173",
                              "http://localhost:5174",
                              "http://192.168.5.128:8700",
                              "https://www.eldia.com",
                              "https://extras.eldia.com",
                              "https://eldia.mustang.cloud"
                          )
                          .AllowAnyHeader()
                          .AllowAnyMethod();
                      });
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

// 3. Construir la aplicación.
var app = builder.Build();

// --- LÓGICA PARA LEER EL NIVEL DE LOGGING AL INICIO ---
// Creamos un scope temporal para leer la configuración de la BD
using (var scope = app.Services.CreateScope()) // O 'host.Services.CreateScope()'
{
    var services = scope.ServiceProvider;
    try
    {
        // El resto de la lógica no cambia
        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);
            Console.WriteLine($"--> Nivel de logging inicial establecido desde la BD a: {logLevelConfig.Valor}");
        }
    }
    catch (Exception ex)
    {
        // Si hay un error (ej. la BD no está disponible al arrancar), se usará el nivel por defecto 'Information'.
        Console.WriteLine($"--> No se pudo establecer el nivel de logging desde la BD: {ex.Message}");
    }
}

app.UseForwardedHeaders();

// Seeder para el usuario admin
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<EleccionesDbContext>();
        var hasher = services.GetRequiredService<IPasswordHasher>();
        if (!context.AdminUsers.Any())
        {
            var (hash, salt) = hasher.HashPassword("PTP847elec");
            context.AdminUsers.Add(new Elecciones.Database.Entities.AdminUser
            {
                Username = "admin",
                PasswordHash = hash,
                PasswordSalt = salt
            });
            context.SaveChanges();
            Console.WriteLine("--> Admin user seeded.");
        }
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while seeding the database.");
    }
}

// Seeder para las bancas vacías
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<EleccionesDbContext>();
    if (!context.Bancadas.Any())
    {
        var bancas = new List<Bancada>();
        // 92 bancas de diputados
        for (int i = 1; i <= 92; i++) // Bucle de 1 a 92
        {
            bancas.Add(new Bancada
            {
                Camara = Elecciones.Core.Enums.TipoCamara.Diputados,
                NumeroBanca = i // Asignamos el número de banca
            });
        }
        // 46 bancas de senadores
        for (int i = 1; i <= 46; i++) // Bucle de 1 a 46
        {
            bancas.Add(new Bancada
            {
                Camara = Elecciones.Core.Enums.TipoCamara.Senadores,
                NumeroBanca = i // Asignamos el número de banca
            });
        }
        context.Bancadas.AddRange(bancas);
        context.SaveChanges();
        Console.WriteLine("--> Seeded 138 bancas físicas.");
    }
}

// Seeder para las configuraciones por defecto
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<EleccionesDbContext>();

    // Lista de configuraciones por defecto a asegurar
    var defaultConfiguraciones = new Dictionary<string, string>
    {
        { "MostrarOcupantes", "true" },
        { "TickerResultadosCantidad", "3" },
        { "ConcejalesResultadosCantidad", "5" },
        { "Worker_Resultados_Activado", "false" },
        { "Worker_Bajas_Activado", "false" },
        { "Worker_Prioridad", "Resultados" },
        { "Logging_Level", "Information" }
    };

    foreach (var config in defaultConfiguraciones)
    {
        if (!context.Configuraciones.Any(c => c.Clave == config.Key))
        {
            context.Configuraciones.Add(new Configuracion { Clave = config.Key, Valor = config.Value });
        }
    }

    context.SaveChanges();
    Console.WriteLine("--> Seeded default configurations.");
}

// --- SEEDER FINAL Y AUTOSUFICIENTE (CON DATOS DE RECUENTO) ---
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<EleccionesDbContext>();
    var logger = services.GetRequiredService<ILogger<Program>>();

    const int eleccionNacionalId = 2;

    if (!context.ResultadosVotos.Any(r => r.EleccionId == eleccionNacionalId))
    {
        logger.LogInformation("--> No se encontraron datos para la elección nacional ID {EleccionId}. Generando datos de simulación COMPLETOS...", eleccionNacionalId);

        var eleccionNacional = await context.Elecciones.FindAsync(eleccionNacionalId) ?? new Eleccion { Id = eleccionNacionalId, Nombre = "Elecciones Nacionales 2025", Nivel = "Nacional", DistritoId = "00", Fecha = new DateOnly(2025, 10, 26) };
        if (!context.Elecciones.Local.Any(e => e.Id == eleccionNacionalId)) context.Elecciones.Add(eleccionNacional);
        
        var categoriaDiputadosNac = await context.CategoriasElectorales.FindAsync(2) ?? new CategoriaElectoral { Id = 2, Nombre = "DIPUTADOS NACIONALES", Orden = 3 };
        if (!context.CategoriasElectorales.Local.Any(c => c.Id == 2)) context.CategoriasElectorales.Add(categoriaDiputadosNac);
        await context.SaveChangesAsync();

        var provinciasMaestras = new Dictionary<string, string>
        {
            { "01", "CIUDAD AUTONOMA DE BUENOS AIRES" }, { "02", "BUENOS AIRES" }, { "03", "CATAMARCA" }, { "04", "CORDOBA" },
            { "05", "CORRIENTES" },{ "06", "CHACO" }, { "07", "CHUBUT" }, { "08", "ENTRE RIOS" },
            { "09", "FORMOSA" }, { "10", "JUJUY" }, { "11", "LA PAMPA" }, { "12", "LA RIOJA" },
            { "13", "MENDOZA" }, { "14", "MISIONES" }, { "15", "NEUQUEN" }, { "16", "RIO NEGRO" },
            { "17", "SALTA" }, { "18", "SAN JUAN" }, { "19", "SAN LUIS" }, { "20", "SANTA CRUZ" },
            { "21", "SANTA FE" }, { "22", "SANTIAGO DEL ESTERO" }, { "23", "TIERRA DEL FUEGO" }, { "24", "TUCUMAN" }
        };

        foreach (var p in provinciasMaestras)
        {
            if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 10 && a.DistritoId == p.Key))
            {
                context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = p.Value, NivelId = 10, DistritoId = p.Key });
            }
        }
        await context.SaveChangesAsync();
        logger.LogInformation("--> Verificados/creados los 24 ámbitos geográficos de Nivel 10.");

        var provinciasEnDb = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync();
        foreach (var provincia in provinciasEnDb)
        {
            if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId))
            {
                logger.LogWarning("--> No se encontraron municipios para {Provincia}. Creando 5 de ejemplo.", provincia.Nombre);
                for (int i = 1; i <= 5; i++)
                {
                    context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = $"{provincia.Nombre} - Depto. {i}", NivelId = 30, DistritoId = provincia.DistritoId });
                }
            }
        }
        await context.SaveChangesAsync();

        var todosLosPartidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync();
        if (!todosLosPartidos.Any()) {
            logger.LogWarning("--> No hay partidos, no se pueden generar votos.");
            return;
        }

        var nuevosResultados = new List<ResultadoVoto>();
        var nuevosEstados = new List<EstadoRecuentoGeneral>();
        var rand = new Random();
        
        long totalVotosNacional = 0;
        int totalMesasNacional = 0;
        int totalMesasEscrutadasNacional = 0;

        foreach (var provincia in provinciasEnDb)
        {
            var municipiosDeProvincia = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId).ToListAsync();
            if (!municipiosDeProvincia.Any()) continue;
            
            long totalVotosProvincia = 0;
            
            int partidoIndex = rand.Next(todosLosPartidos.Count);
            foreach (var municipio in municipiosDeProvincia)
            {
                var partidoGanador = todosLosPartidos[partidoIndex++ % todosLosPartidos.Count];
                var votosGanador = rand.Next(25000, 70000);
                nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoriaDiputadosNac.Id, AgrupacionPoliticaId = partidoGanador.Id, CantidadVotos = votosGanador });
                totalVotosProvincia += votosGanador;

                var otrosPartidos = todosLosPartidos.Where(p => p.Id != partidoGanador.Id).OrderBy(p => rand.Next()).Take(rand.Next(3, todosLosPartidos.Count));
                foreach (var competidor in otrosPartidos) {
                    var votosCompetidor = rand.Next(1000, 24000);
                    nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoriaDiputadosNac.Id, AgrupacionPoliticaId = competidor.Id, CantidadVotos = votosCompetidor });
                    totalVotosProvincia += votosCompetidor;
                }
            }

            // --- LÓGICA DE DATOS DE RECUENTO POR PROVINCIA ---
            var mesasEsperadasProvincia = municipiosDeProvincia.Count * rand.Next(15, 30);
            var mesasTotalizadasProvincia = (int)(mesasEsperadasProvincia * (rand.Next(75, 99) / 100.0));
            var cantidadElectoresProvincia = mesasEsperadasProvincia * 350;
            var participacionProvincia = (decimal)(rand.Next(65, 85) / 100.0);

            nuevosEstados.Add(new EstadoRecuentoGeneral {
                EleccionId = eleccionNacionalId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoriaDiputadosNac.Id,
                FechaTotalizacion = DateTime.UtcNow,
                MesasEsperadas = mesasEsperadasProvincia,
                MesasTotalizadas = mesasTotalizadasProvincia,
                MesasTotalizadasPorcentaje = (decimal)mesasTotalizadasProvincia * 100 / mesasEsperadasProvincia,
                CantidadElectores = cantidadElectoresProvincia,
                CantidadVotantes = (int)(cantidadElectoresProvincia * participacionProvincia),
                ParticipacionPorcentaje = participacionProvincia * 100
            });
            
            totalVotosNacional += totalVotosProvincia;
            totalMesasNacional += mesasEsperadasProvincia;
            totalMesasEscrutadasNacional += mesasTotalizadasProvincia;
        }

        // --- LÓGICA DE DATOS DE RECUENTO A NIVEL NACIONAL ---
        var ambitoNacional = await context.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 0);
        if (ambitoNacional == null) {
            ambitoNacional = new AmbitoGeografico { Nombre = "Nacional", NivelId = 0, DistritoId = "00" };
            context.AmbitosGeograficos.Add(ambitoNacional);
            await context.SaveChangesAsync();
        }
        var participacionNacional = (decimal)(rand.Next(70, 88) / 100.0);
        nuevosEstados.Add(new EstadoRecuentoGeneral {
            EleccionId = eleccionNacionalId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoriaDiputadosNac.Id,
            FechaTotalizacion = DateTime.UtcNow,
            MesasEsperadas = totalMesasNacional,
            MesasTotalizadas = totalMesasEscrutadasNacional,
            MesasTotalizadasPorcentaje = (decimal)totalMesasEscrutadasNacional * 100 / totalMesasNacional,
            CantidadElectores = totalMesasNacional * 350,
            CantidadVotantes = (int)((totalMesasNacional * 350) * participacionNacional),
            ParticipacionPorcentaje = participacionNacional * 100
        });

        if (nuevosResultados.Any()) {
            await context.ResultadosVotos.AddRangeAsync(nuevosResultados);
            await context.EstadosRecuentosGenerales.AddRangeAsync(nuevosEstados);
            await context.SaveChangesAsync();
            logger.LogInformation("--> Se generaron {Votos} registros de votos y {Estados} de estados de recuento.", nuevosResultados.Count, nuevosEstados.Count);
        } else {
            logger.LogWarning("--> No se generaron datos de simulación.");
        }
    }
}

// Configurar el pipeline de peticiones HTTP.
// Añadimos el logging de peticiones de Serilog aquí.
app.UseSerilogRequestLogging();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// 1. Redirección a HTTPS (si se usa)
//app.UseHttpsRedirection();

// 2. Middleware de Enrutamiento. ¡CLAVE!
//    Determina a qué endpoint irá la petición.
app.UseRouting();

// 3. Middleware de CORS.
//    Ahora se ejecuta sabiendo a qué endpoint se dirige la petición.
app.UseCors(MyAllowSpecificOrigins);

// 4. Middleware de Autenticación.
//    Identifica quién es el usuario a partir del token.
app.UseAuthentication();

// 5. Middleware de Autorización.
//    Verifica si el usuario identificado tiene permiso para acceder al endpoint.
app.UseAuthorization();

// 6. Mapea los controladores a los endpoints que el enrutador descubrió.
app.MapControllers();

// 5. Ejecutar la aplicación.
app.Run();