using System.Text.Json;
using CoreUpload.Configuration;
using CoreUpload.Demos.DemoSecurity;
using CoreUpload.Endpoints;
using CoreUpload.Middleware;
using CoreUpload.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-XSRF-TOKEN";
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = "CoreUpload.Demos.Auth";
        options.Events.OnRedirectToLogin = context =>
        {
            if (context.Request.Path.StartsWithSegments("/demo/security"))
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return context.Response.WriteAsJsonAsync(new
                {
                    success = false,
                    errorMessage = "Sign in as a demo user or admin to upload through this endpoint."
                });
            }

            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
        options.Events.OnRedirectToAccessDenied = context =>
        {
            if (context.Request.Path.StartsWithSegments("/demo/security"))
            {
                context.Response.StatusCode = StatusCodes.Status403Forbidden;
                return context.Response.WriteAsJsonAsync(new
                {
                    success = false,
                    errorMessage = "Your current demo account is not allowed to use this endpoint."
                });
            }

            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
    });
builder.Services.AddAuthorization();
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.OnRejected = (context, token) =>
    {
        context.HttpContext.Response.ContentType = "application/json";
        return new ValueTask(context.HttpContext.Response.WriteAsJsonAsync(new
        {
            success = false,
            errorMessage = "Demo rate limit reached. Wait 15 seconds and try again."
        }, cancellationToken: token));
    };

    options.AddFixedWindowLimiter(DemoSecurityEndpoints.RateLimitPolicyName, policy =>
    {
        policy.PermitLimit = 3;
        policy.Window = TimeSpan.FromSeconds(15);
        policy.QueueLimit = 0;
        policy.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
    });
});
// Save every successful upload to wwwroot/uploads - see SaveUploadsToDisk.cs.
// Uploads are TEMPORARY until an IUploadEventHandler (or your endpoint code)
// persists them; without this line the demos would "succeed" and save nothing.
builder.Services.AddSingleton<CoreUpload.Events.IUploadEventHandler, CoreUpload.Demos.SaveUploadsToDisk>();

builder.Services.AddCoreUpload(options =>
{
    options.TempDirectory = "UploaderTemp";
    options.MaxFileSizeBytes = 5L * 1024 * 1024 * 1024;
    options.ChunkSizeBytes = 5 * 1024 * 1024;
    options.EnableInstantUpload = true;   // powers demo 124 (Instant Upload)
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCoreUpload();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();

app.MapRazorPages();
app.MapDemoSecurityEndpoints();
app.MapGet("/demo/sse/progress/{uploadId}", async (string uploadId, HttpContext context, IUploadProgressService progressService) =>
{
    context.Response.Headers["Cache-Control"] = "no-cache";
    context.Response.Headers["Connection"] = "keep-alive";
    context.Response.ContentType = "text/event-stream";

    string? lastPayload = null;

    while (!context.RequestAborted.IsCancellationRequested)
    {
        var info = progressService.Get(uploadId);
        var payload = JsonSerializer.Serialize(new
        {
            uploadId,
            fileName = info?.FileName,
            status = info?.Status ?? "pending",
            chunk = info?.ChunksCompleted ?? 0,
            totalChunks = info?.TotalChunks ?? 0,
            uploadedBytes = info?.UploadedBytes ?? 0,
            totalBytes = info?.TotalBytes ?? 0,
            percent = info?.PercentComplete ?? 0,
            error = info?.ErrorMessage
        });

        if (!string.Equals(payload, lastPayload, StringComparison.Ordinal))
        {
            await context.Response.WriteAsync($"data: {payload}\n\n", context.RequestAborted);
            await context.Response.Body.FlushAsync(context.RequestAborted);
            lastPayload = payload;
        }

        if (info != null && (string.Equals(info.Status, "completed", StringComparison.OrdinalIgnoreCase) ||
                             string.Equals(info.Status, "failed", StringComparison.OrdinalIgnoreCase)))
        {
            break;
        }

        await Task.Delay(250, context.RequestAborted);
    }
});
app.MapCoreUploadEndpoints();

app.Run();
