using System.Text;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Options;
using OTSSignsOrchestrator.Server.Api;
using OTSSignsOrchestrator.Server.Auth;
using OTSSignsOrchestrator.Server.Clients;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Hubs;
using OTSSignsOrchestrator.Server.Reports;
using OTSSignsOrchestrator.Server.Services;
using OTSSignsOrchestrator.Server.Webhooks;
using OTSSignsOrchestrator.Server.Workers;
using Refit;
using Quartz;
using OTSSignsOrchestrator.Server.Jobs;
using OTSSignsOrchestrator.Server.Health;
using OTSSignsOrchestrator.Server.Health.Checks;
using Serilog;
using Stripe;

var builder = WebApplication.CreateBuilder(args);

// ── Serilog ──────────────────────────────────────────────────────────────────
builder.Host.UseSerilog((context, config) =>
    config.ReadFrom.Configuration(context.Configuration));

// ── EF Core — PostgreSQL ─────────────────────────────────────────────────────
builder.Services.AddDbContext<OrchestratorDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("OrchestratorDb")));

// ── JWT Authentication ──────────────────────────────────────────────────────
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.Section));
var jwtKey = builder.Configuration[$"{JwtOptions.Section}:Key"]
    ?? throw new InvalidOperationException("Jwt:Key must be configured.");

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration[$"{JwtOptions.Section}:Issuer"] ?? "OTSSignsOrchestrator",
            ValidAudience = builder.Configuration[$"{JwtOptions.Section}:Audience"] ?? "OTSSignsOrchestrator",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)),
            ClockSkew = TimeSpan.FromSeconds(30),
        };

        // Allow SignalR to receive the JWT via query string
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
                    context.Token = accessToken;
                return Task.CompletedTask;
            },
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomerPortal", policy =>
        policy.RequireClaim("customer_id"));
});

// ── Application services ────────────────────────────────────────────────────
builder.Services.AddScoped<OperatorAuthService>();
builder.Services.AddScoped<AbbreviationService>();
builder.Services.Configure<EmailOptions>(builder.Configuration.GetSection(EmailOptions.Section));
builder.Services.AddSingleton<EmailService>();

// ── Report services ─────────────────────────────────────────────────────────
builder.Services.AddScoped<BillingReportService>();
builder.Services.AddScoped<FleetHealthPdfService>();

// ── Provisioning pipelines + worker ─────────────────────────────────────────
builder.Services.AddScoped<IProvisioningPipeline, Phase1Pipeline>();
builder.Services.AddScoped<IProvisioningPipeline, Phase2Pipeline>();
builder.Services.AddScoped<IProvisioningPipeline, ByoiSamlPipeline>();
builder.Services.AddScoped<IProvisioningPipeline, SuspendPipeline>();
builder.Services.AddScoped<IProvisioningPipeline, ReactivatePipeline>();
builder.Services.AddScoped<IProvisioningPipeline, UpdateScreenLimitPipeline>();
builder.Services.AddScoped<IProvisioningPipeline, DecommissionPipeline>();
builder.Services.AddScoped<IProvisioningPipeline, RotateCredentialsPipeline>();
builder.Services.AddHostedService<ProvisioningWorker>();

// ── External API clients ────────────────────────────────────────────────────
builder.Services.AddHttpClient();
builder.Services.AddSingleton<XiboClientFactory>();

builder.Services.Configure<AuthentikOptions>(
    builder.Configuration.GetSection(AuthentikOptions.Section));

builder.Services.AddRefitClient<IAuthentikClient>()
    .ConfigureHttpClient((sp, client) =>
    {
        var opts = sp.GetRequiredService<IOptions<AuthentikOptions>>().Value;
        client.BaseAddress = new Uri(opts.BaseUrl.TrimEnd('/'));
        client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", opts.ApiToken);
    });

// ── Stripe ──────────────────────────────────────────────────────────────────
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];

// ── Health check engine + individual checks ─────────────────────────────────
builder.Services.AddHostedService<HealthCheckEngine>();
builder.Services.AddScoped<IHealthCheck, XiboApiHealthCheck>();
builder.Services.AddScoped<IHealthCheck, AdminIntegrityHealthCheck>();
builder.Services.AddScoped<IHealthCheck, GroupStructureHealthCheck>();
builder.Services.AddScoped<IHealthCheck, OauthAppHealthCheck>();
builder.Services.AddScoped<IHealthCheck, DisplayAuthorisedHealthCheck>();
builder.Services.AddScoped<IHealthCheck, StackHealthCheck>();
builder.Services.AddScoped<IHealthCheck, MySqlConnectHealthCheck>();
builder.Services.AddScoped<IHealthCheck, NfsAccessHealthCheck>();
builder.Services.AddScoped<IHealthCheck, ThemeHealthCheck>();
builder.Services.AddScoped<IHealthCheck, XiboVersionHealthCheck>();
builder.Services.AddScoped<IHealthCheck, OauthAppAgeHealthCheck>();
builder.Services.AddScoped<IHealthCheck, ByoiCertExpiryHealthCheck>();
builder.Services.AddScoped<IHealthCheck, AuthentikGlobalHealthCheck>();
builder.Services.AddScoped<IHealthCheck, AuthentikSamlProviderHealthCheck>();
builder.Services.AddScoped<IHealthCheck, InvitationFlowHealthCheck>();
// AuthentikGlobalHealthCheck also registered as concrete type for the Quartz job
builder.Services.AddScoped<AuthentikGlobalHealthCheck>();

// ── Quartz scheduler ─────────────────────────────────────────────────────────
builder.Services.AddQuartz(q =>
{
    var certExpiryKey = new JobKey("byoi-cert-expiry-global", "byoi-cert-expiry");
    q.AddJob<ByoiCertExpiryJob>(opts => opts.WithIdentity(certExpiryKey).StoreDurably());
    q.AddTrigger(opts => opts
        .ForJob(certExpiryKey)
        .WithIdentity("byoi-cert-expiry-global-trigger", "byoi-cert-expiry")
        .WithSimpleSchedule(s => s
            .WithIntervalInHours(24)
            .RepeatForever())
        .StartNow());

    // Authentik global health check — every 2 minutes
    var authentikHealthKey = new JobKey("authentik-global-health", "health-checks");
    q.AddJob<AuthentikGlobalHealthJob>(opts => opts.WithIdentity(authentikHealthKey).StoreDurably());
    q.AddTrigger(opts => opts
        .ForJob(authentikHealthKey)
        .WithIdentity("authentik-global-health-trigger", "health-checks")
        .WithSimpleSchedule(s => s
            .WithIntervalInMinutes(2)
            .RepeatForever())
        .StartNow());

    // Daily screen snapshot — 2 AM UTC
    var dailySnapshotKey = new JobKey("daily-snapshot", "snapshots");
    q.AddJob<OTSSignsOrchestrator.Server.Jobs.DailySnapshotJob>(opts => opts.WithIdentity(dailySnapshotKey).StoreDurably());
    q.AddTrigger(opts => opts
        .ForJob(dailySnapshotKey)
        .WithIdentity("daily-snapshot-trigger", "snapshots")
        .WithCronSchedule("0 0 2 * * ?"));

    // Scheduled reports — weekly (Monday 08:00 UTC) + monthly (1st 08:00 UTC)
    var reportJobKey = new JobKey("scheduled-report", "reports");
    q.AddJob<ScheduledReportJob>(opts => opts.WithIdentity(reportJobKey).StoreDurably());
    q.AddTrigger(opts => opts
        .ForJob(reportJobKey)
        .WithIdentity("weekly-report-trigger", "reports")
        .UsingJobData(ScheduledReportJob.IsMonthlyKey, false)
        .WithCronSchedule("0 0 8 ? * MON *"));
    q.AddTrigger(opts => opts
        .ForJob(reportJobKey)
        .WithIdentity("monthly-report-trigger", "reports")
        .UsingJobData(ScheduledReportJob.IsMonthlyKey, true)
        .WithCronSchedule("0 0 8 1 * ? *"));
});
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);

// ── SignalR ──────────────────────────────────────────────────────────────────
builder.Services.AddSignalR();

// ── Rate limiting ────────────────────────────────────────────────────────────
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = 429;

    options.AddFixedWindowLimiter("fixed", limiter =>
    {
        limiter.PermitLimit = 60;
        limiter.Window = TimeSpan.FromMinutes(1);
    });

    options.AddSlidingWindowLimiter("signup", limiter =>
    {
        limiter.PermitLimit = 3;
        limiter.Window = TimeSpan.FromMinutes(10);
        limiter.SegmentsPerWindow = 2;
    });
});

var app = builder.Build();

// ── Middleware ────────────────────────────────────────────────────────────────
app.UseSerilogRequestLogging();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();

// ── Auth endpoints (no auth required) ────────────────────────────────────────
app.MapPost("/api/auth/login", async (LoginRequest req, OperatorAuthService auth) =>
{
    try
    {
        var (jwt, refresh) = await auth.LoginAsync(req.Email, req.Password);
        return Results.Ok(new { token = jwt, refreshToken = refresh });
    }
    catch (UnauthorizedAccessException)
    {
        return Results.Unauthorized();
    }
});

app.MapPost("/api/auth/refresh", async (RefreshRequest req, OperatorAuthService auth) =>
{
    try
    {
        var jwt = await auth.RefreshAsync(req.RefreshToken);
        return Results.Ok(new { token = jwt });
    }
    catch (UnauthorizedAccessException)
    {
        return Results.Unauthorized();
    }
});

// ── Signup endpoints (no auth) ──────────────────────────────────────────────
app.MapSignupEndpoints();

// ── Stripe webhook (no auth, no rate limit) ─────────────────────────────────
app.MapStripeWebhook();

// ── Fleet + Jobs REST endpoints (auth required) ─────────────────────────────
app.MapFleetEndpoints();
// ── Customer Portal BYOI endpoints (customer JWT required) ──────────────
app.MapCustomerPortalEndpoints();
// ── SignalR hub ─────────────────────────────────────────────────────────────
app.MapHub<FleetHub>("/hubs/fleet");

app.Run();

// ── Request DTOs for auth endpoints ─────────────────────────────────────────
public record LoginRequest(string Email, string Password);
public record RefreshRequest(string RefreshToken);
