using System.Net.Http.Headers;
using System.Text;
using ApiDenuncias.Configuration;
using ApiDenuncias.Security;
using ApiDenuncias.Services;
using GestionaDenuncias.Shared.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile(
    $"appsettings.{builder.Environment.EnvironmentName}.local.json",
    optional: true,
    reloadOnChange: false);

builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.SectionName));
builder.Services.Configure<KeyVaultOptions>(builder.Configuration.GetSection(KeyVaultOptions.SectionName));
builder.Services.Configure<GestionaOptions>(builder.Configuration.GetSection("Gestiona"));
builder.Services.Configure<GlobalLeaksOptions>(builder.Configuration.GetSection(GlobalLeaksOptions.SectionName));
builder.Services.Configure<ComplaintStorageOptions>(builder.Configuration.GetSection(ComplaintStorageOptions.SectionName));
builder.Services.Configure<ManualPurgeOptions>(builder.Configuration.GetSection(ManualPurgeOptions.SectionName));
builder.Services.Configure<OpeBridgeOptions>(builder.Configuration.GetSection(OpeBridgeOptions.SectionName));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDataProtection()
    .SetApplicationName("ApiDenuncias");

builder.Services.AddSingleton<LoginRateLimiter>();
builder.Services.AddSingleton<GlobalLeaksSessionStore>();
builder.Services.AddSingleton<PendingGlobalLeaksLoginStore>();
builder.Services.AddScoped<GlobalLeaksClient>();
builder.Services.AddScoped<GlobalLeaksSessionKeepAliveService>();
builder.Services.AddSingleton<MySqlConnectionStringProvider>();
builder.Services.AddScoped<MySqlDenunciaStore>();
builder.Services.AddSingleton<IEncryptionKeyProvider, KeyVaultEncryptionKeyProvider>();
builder.Services.AddSingleton<IEnvelopeEncryptionKeyProvider, EnvelopeEncryptionKeyProvider>();
builder.Services.AddScoped<EncryptedDenunciaStore>();
builder.Services.AddScoped<IDenunciaStore>(sp => sp.GetRequiredService<EncryptedDenunciaStore>());
builder.Services.AddScoped<IFilteredDenunciaStore>(sp => sp.GetRequiredService<EncryptedDenunciaStore>());
builder.Services.AddScoped<IInboxTrackingService, InboxTrackingService>();
builder.Services.AddScoped<DenunciaInboxService>();
builder.Services.AddScoped<GestionaDocumentWorkflowService>();
builder.Services.AddScoped<GestionaExpedienteExceptionStore>();
builder.Services.AddScoped<UserComplaintAccessService>();
builder.Services.AddScoped<WorkGroupAdministrationService>();
builder.Services.AddHttpClient<ManualPurgeService>();
builder.Services.AddScoped<AppConfigurationService>();
builder.Services.AddScoped<OpeBridgeApiKeyFilter>();

builder.Services.AddHttpClient<IGestionaService, GestionaService>((sp, client) =>
{
    var opts = sp.GetRequiredService<IOptions<GestionaOptions>>().Value;
    client.BaseAddress = new Uri(opts.ApiBase);
    client.DefaultRequestHeaders.Add("X-Gestiona-Access-Token", opts.AccessToken);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});

var jwt = builder.Configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>() ?? new JwtOptions();
var signingKey = Encoding.UTF8.GetBytes(jwt.SigningKey);

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = jwt.RequireHttpsMetadata;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = jwt.Issuer,
            ValidAudience = jwt.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(signingKey),
            ClockSkew = TimeSpan.FromMinutes(1)
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILoggerFactory>()
                    .CreateLogger("ApiDenuncias.Jwt");
                logger.LogWarning(context.Exception, "JWT no valido en {Path}", context.HttpContext.Request.Path);
                return Task.CompletedTask;
            },
            OnChallenge = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILoggerFactory>()
                    .CreateLogger("ApiDenuncias.Jwt");
                logger.LogWarning(
                    "JWT rechazado en {Path}. Error={Error}. Description={Description}. AuthorizationHeader={HasAuthorizationHeader}",
                    context.HttpContext.Request.Path,
                    context.Error,
                    context.ErrorDescription,
                    context.Request.Headers.ContainsKey("Authorization"));
                return Task.CompletedTask;
            }
        };
    });

var configurationAdministrators = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
    "pcornejo",
    "Rgarciaglbk",
    "eaguilarGestor"
};

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ConfigurationAdministrators", policy =>
    {
        policy.RequireAssertion(context =>
        {
            var username = context.User.Identity?.Name?.Trim();
            return context.User.Identity?.IsAuthenticated == true &&
                   !string.IsNullOrWhiteSpace(username) &&
                   configurationAdministrators.Contains(username);
        });
    });
});

var app = builder.Build();

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var feature = context.Features.Get<IExceptionHandlerFeature>();
        var logger = context.RequestServices
            .GetRequiredService<ILoggerFactory>()
            .CreateLogger("ApiDenuncias.UnhandledException");

        if (feature?.Error is not null)
        {
            logger.LogError(feature.Error, "Error no controlado en {Path}", context.Request.Path);
        }

        if (feature?.Error is EncryptedDataPurgedException purgedException)
        {
            context.Response.StatusCode = StatusCodes.Status410Gone;
            await context.Response.WriteAsJsonAsync(new ApiError(purgedException.Message));
            return;
        }

        if (feature?.Error is EncryptionKeyUnavailableException keyException)
        {
            context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
            await context.Response.WriteAsJsonAsync(new ApiError(keyException.Message));
            return;
        }

        var detailedErrors = context.RequestServices
            .GetRequiredService<IConfiguration>()
            .GetValue("DetailedApiErrors", false);
        var message = detailedErrors && feature?.Error is not null
            ? $"La API de denuncias no ha podido completar la operacion: {feature.Error.GetType().Name}: {feature.Error.Message}"
            : "La API de denuncias no ha podido completar la operacion.";

        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        await context.Response.WriteAsJsonAsync(new ApiError(message));
    });
});

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

if (builder.Configuration.GetValue("ForceHttpsRedirection", false))
{
    app.UseHttpsRedirection();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/health", () => Results.Ok(new { status = "ok" })).AllowAnonymous();
app.MapControllers();

app.Run();
