using NexusReader.Web.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Components;
using NexusReader.Application;
using NexusReader.Infrastructure;
using NexusReader.Application.Abstractions.Services;
using NexusReader.Application.Queries.User;
using NexusReader.Application.Commands.Library;
using NexusReader.Application.Queries.Library;
using NexusReader.Application.Queries.Concepts;
using MediatR;
using NexusReader.Web.Client.Services;
using NexusReader.UI.Shared.Services;
using NexusReader.Domain.Entities;
using NexusReader.Data.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using NexusReader.Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using NexusReader.Infrastructure.Services;
using Stripe;
using Microsoft.Extensions.AI;
using NexusReader.Application.Abstractions.Persistence;
using NexusReader.Application.Abstractions.Messaging;
using Hangfire;

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

// Enable detailed circuit errors for Server‑Side Blazor components
builder.Services.AddServerSideBlazor()
    .AddCircuitOptions(options =>
    {
        options.DetailedErrors = true;
    });
builder.Services.AddSignalR();
builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<IPlatformService, WebPlatformService>();
builder.Services.AddScoped<INativeStorageService, NexusReader.Web.Services.NativeStorageService>();
builder.Services.AddScoped<IThemeService, ThemeService>();
builder.Services.AddScoped<IQuizStateService, QuizStateService>();
builder.Services.AddScoped<IFocusModeService, FocusModeService>();
builder.Services.AddScoped<IReaderNavigationService, ReaderNavigationService>();
builder.Services.AddScoped<IKnowledgeGraphService, KnowledgeGraphService>();
builder.Services.AddScoped<IReaderInteractionService, ReaderInteractionService>();
builder.Services.AddScoped<KnowledgeCoordinator>();
builder.Services.AddScoped<ISyncService, SyncService>();

builder.Services.AddHttpClient("NexusAPI", (sp, client) => 
{
    var configuration = sp.GetRequiredService<IConfiguration>();
    var apiBaseUrl = configuration["ApiBaseUrl"];
    if (!string.IsNullOrEmpty(apiBaseUrl))
    {
        client.BaseAddress = new Uri(apiBaseUrl);
    }
    else
    {
        // For local development/Interactive Server, we use the current base address
        var nav = sp.GetRequiredService<NavigationManager>();
        client.BaseAddress = new Uri(nav.BaseUri);
    }
});
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("NexusAPI"));

builder.Services.AddScoped<IIdentityService, NexusReader.Web.Services.ServerIdentityService>();
builder.Services.AddScoped<IConceptsMapService, NexusReader.Web.Services.ServerConceptsMapService>();
builder.Services.AddCascadingAuthenticationState();

builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(
    NexusReader.Application.DependencyInjection.Assembly,
    NexusReader.Infrastructure.DependencyInjection.Assembly
));

// Authorization Policies
builder.Services.AddScoped<IAuthorizationHandler, TokenLimitHandler>();
builder.Services.AddAuthorizationBuilder()
    .SetDefaultPolicy(new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(
        IdentityConstants.ApplicationScheme,
        IdentityConstants.BearerScheme)
        .RequireAuthenticatedUser()
        .Build())
    .AddPolicy("ProUser", policy => policy.RequireClaim("Plan", SubscriptionPlan.ProName, SubscriptionPlan.EnterpriseName))
    .AddPolicy("HasAvailableTokens", policy => policy.AddRequirements(new TokenLimitRequirement()));

// Billing & Stripe
builder.Services.AddScoped<IBillingService, NexusReader.Infrastructure.Services.BillingService>();

// Authentication
builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddGoogle(options =>
    {
        options.ClientId = builder.Configuration["Authentication:Google:ClientId"] ?? "placeholder-id";
        options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"] ?? "placeholder-secret";
    });

builder.Services.AddIdentityApiEndpoints<NexusUser>()
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<AppDbContext>()
    .AddClaimsPrincipalFactory<NexusReader.Web.Services.CustomUserClaimsPrincipalFactory>();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/account/login";
    options.LogoutPath = "/account/logout";
    options.AccessDeniedPath = "/account/access-denied";
    options.Cookie.Name = "NexusReader.Auth";
    options.Cookie.HttpOnly = true;
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
    options.ExpireTimeSpan = TimeSpan.FromDays(30);
    options.SlidingExpiration = true;

    options.Events.OnRedirectToLogin = context =>
    {
        var isApiRequest = context.Request.Path.StartsWithSegments("/api") || 
                           context.Request.Path.StartsWithSegments("/identity") ||
                           context.Request.Headers["Accept"].ToString().Contains("application/json");

        if (isApiRequest)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        }
        else
        {
            context.Response.Redirect(context.RedirectUri);
        }
        return Task.CompletedTask;
    };
});

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password settings
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequiredLength = 8;
    options.Password.RequiredUniqueChars = 1;

    // Lockout settings
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings
    options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = true;
});

var app = builder.Build();

app.UseHangfireDashboard();

// Startup Validation
using (var scope = app.Services.CreateScope())
{
    var marker = scope.ServiceProvider.GetService<IInfrastructureMarker>();
    if (marker == null)
    {
        throw new InvalidOperationException("CRITICAL: Infrastructure layer was not registered. Ensure AddInfrastructure() is called in Program.cs.");
    }
}

// Ensure Database is initialized and seeded
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var logger = services.GetRequiredService<ILogger<Program>>();
    var dbContextFactory = services.GetRequiredService<IDbContextFactory<NexusReader.Data.Persistence.AppDbContext>>();
    using var dbContext = await dbContextFactory.CreateDbContextAsync();
    
    int maxRetries = 5;
    int delayMs = 2000;
    
    for (int i = 0; i < maxRetries; i++)
    {
        try
        {
            if (logger.IsEnabled(LogLevel.Information))
            {
                logger.LogInformation("Próba połączenia z bazą danych (próba {Attempt}/{MaxRetries})...", i + 1, maxRetries);
            }

            await dbContext.Database.MigrateAsync();
            await DbInitializer.SeedAsync(services);
            await TriggerBackgroundProcessingForUnindexedBooksAsync(services);

            if (logger.IsEnabled(LogLevel.Information))
            {
                logger.LogInformation("Baza danych zainicjowana pomyślnie.");
            }
            break;
        }
        catch (Npgsql.NpgsqlException ex) when (i < maxRetries - 1)
        {
            if (logger.IsEnabled(LogLevel.Warning))
            {
                logger.LogWarning(ex, "Błąd połączenia z bazą danych. Ponowna próba za {Delay}ms...", delayMs);
            }

            await Task.Delay(delayMs);
            delayMs *= 2; // Exponential backoff
        }
        catch (Exception ex)
        {
            if (logger.IsEnabled(LogLevel.Critical))
            {
                logger.LogCritical(ex, "Krytyczny błąd podczas inicjalizacji bazy danych.");
            }
            throw;
        }
    }
}

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
if (!app.Environment.IsDevelopment())
{
    app.UseHttpsRedirection();
}

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

// Feature flags: block registration & password reset in restricted environments (e.g. Test)
var allowRegistration = app.Configuration.GetValue<bool?>("Features:AllowRegistration") ?? true;
var allowPasswordReset = app.Configuration.GetValue<bool?>("Features:AllowPasswordReset") ?? true;

if (!allowRegistration || !allowPasswordReset)
{
    app.Use(async (context, next) =>
    {
        var path = context.Request.Path.Value?.ToLowerInvariant();

        if (!allowRegistration && path is "/identity/register" or "/account/register")
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsync("Registration is disabled in this environment.");
            return;
        }

        if (!allowPasswordReset && path is "/identity/forgotpassword" or "/account/forgot-password")
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsync("Password reset is disabled in this environment.");
            return;
        }

        await next();
    });
}

app.MapStaticAssets();
app.MapHub<NexusReader.Infrastructure.RealTime.SyncHub>("/synchub");

// API endpoint for WASM client to fetch EPUB content
app.MapGet("/api/epub/{ebookId:guid}/{index:int}", async (Guid ebookId, int index, IEpubReader epubService, ClaimsPrincipal user) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    var result = await epubService.GetEpubContentAsync(ebookId, index, userId);
    
    if (result.IsSuccess) return Results.Ok(result.Value);
    
    var errorMsg = result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error";
    return Results.BadRequest(errorMsg);
}).RequireAuthorization();

var knowledgeApi = app.MapGroup("/api/knowledge")
    .RequireAuthorization("HasAvailableTokens")
    .DisableAntiforgery();

knowledgeApi.MapPost("/", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.GetKnowledgeAsync(request.Text, tenantId, request.EbookId);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/graph", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.GetGraphDataAsync(request.Text, tenantId, request.EbookId);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/summary", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.GetSummaryAndQuizAsync(request.Text, tenantId, request.EbookId);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/map", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.GetKnowledgeMapAsync(request.Text, tenantId, request.EbookId);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/verify-groundedness", async (GroundednessRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.VerifyGroundednessAsync(request.Answer, request.Context, tenantId);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/search", async (SemanticSearchRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.SearchLibrarySemanticallyAsync(request.QueryText, tenantId, request.Limit);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapPost("/ask", async (AskQuestionRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
    var tenantId = user.FindFirstValue("TenantId") ?? "global";
    var result = await knowledgeService.AskQuestionAsync(request.Question, tenantId, request.EbookId, request.Limit);
    if (result.IsSuccess) return Results.Ok(result.Value);
    return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});

knowledgeApi.MapDelete("/", async (IKnowledgeService knowledgeService) =>
{
    var result = await knowledgeService.ClearCacheAsync();
    if (result.IsSuccess) return Results.Ok();
    
    var errorMsg = result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error";
    return Results.BadRequest(errorMsg);
});

app.MapPost("/api/library/ingest", async ([FromBody] IngestEbookRequest request, ClaimsPrincipal user, IMediator mediator) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    if (string.IsNullOrEmpty(userId)) return Results.Unauthorized();

    var epubData = Convert.FromBase64String(request.EpubDataBase64);
    byte[]? coverData = !string.IsNullOrEmpty(request.CoverImageBase64) 
        ? Convert.FromBase64String(request.CoverImageBase64) 
        : null;

    var tenantId = user.FindFirst("TenantId")?.Value ?? "global";

    var command = new IngestEbookCommand(
        request.Title,
        request.AuthorName,
        coverData,
        epubData,
        request.Description,
        userId,
        tenantId
    );

    var result = await mediator.Send(command);
    if (result.IsSuccess) return Results.Ok(new { Id = result.Value });

    return Results.BadRequest(result.Errors.FirstOrDefault()?.Message ?? "Ingestion failed");
}).RequireAuthorization().DisableAntiforgery();

app.MapGet("/api/library/books", async (ClaimsPrincipal user, IMediator mediator) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    if (string.IsNullOrEmpty(userId)) return Results.Unauthorized();

    var result = await mediator.Send(new GetMyEbooksQuery(userId));
    if (result.IsSuccess) return Results.Ok(result.Value);

    var errorMsg = result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error";
    return Results.BadRequest(errorMsg);
}).RequireAuthorization();

app.MapGet("/api/book/{bookId:guid}/concepts-map", async (
    Guid bookId,
    ClaimsPrincipal user,
    IMediator mediator) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    var tenantId = user.FindFirstValue("TenantId") ?? "global";

    if (string.IsNullOrEmpty(userId))
    {
        return Results.Unauthorized();
    }

    var result = await mediator.Send(new GetBookConceptsMapQuery(bookId, userId, tenantId));
    if (result.IsFailed)
    {
        return Results.BadRequest(result.Errors.FirstOrDefault()?.Message ?? "Failed to fetch concepts map.");
    }

    return Results.Ok(result.Value);
}).RequireAuthorization();

app.MapPost("/api/StripeWebhook", async (
    HttpContext context, 
    UserManager<NexusUser> userManager, 
    IConfiguration configuration,
    IDbContextFactory<AppDbContext> dbContextFactory) =>
{
    using var dbContext = await dbContextFactory.CreateDbContextAsync();
    var json = await new StreamReader(context.Request.Body).ReadToEndAsync();
    var webhookSecret = configuration["Stripe:WebhookSecret"] ?? "";

    try
    {
        var stripeEvent = EventUtility.ConstructEvent(
            json,
            context.Request.Headers["Stripe-Signature"],
            webhookSecret
        );

        switch (stripeEvent.Type)
        {
            case EventTypes.CheckoutSessionCompleted:
                var session = stripeEvent.Data.Object as Stripe.Checkout.Session;
                await HandleSubscriptionSuccess(session?.CustomerEmail, session?.Metadata, userManager, dbContext);
                break;

            case EventTypes.CustomerSubscriptionUpdated:
                var subscription = stripeEvent.Data.Object as Stripe.Subscription;
                await HandleSubscriptionSuccess(subscription?.Metadata["CustomerEmail"], subscription?.Metadata, userManager, dbContext);
                break;

            case EventTypes.CustomerSubscriptionDeleted:
                var deletedSubscription = stripeEvent.Data.Object as Stripe.Subscription;
                await HandleSubscriptionCancellation(deletedSubscription?.Metadata["CustomerEmail"], userManager, dbContext);
                break;
        }
        return Results.Ok();
    }
    catch (StripeException e)
    {
        return Results.BadRequest(e.Message);
    }
}).DisableAntiforgery();

async Task HandleSubscriptionSuccess(
    string? email, 
    Dictionary<string, string>? metadata, 
    UserManager<NexusUser> userManager,
    AppDbContext dbContext)
{
    if (string.IsNullOrEmpty(email)) return;

    var user = await userManager.FindByEmailAsync(email);
    if (user != null)
    {
        var planName = metadata?.GetValueOrDefault("Plan") ?? SubscriptionPlan.ProName;
        var plan = await dbContext.SubscriptionPlans.FirstOrDefaultAsync(p => p.PlanName == planName);
        
        if (plan != null)
        {
            user.SubscriptionPlanId = plan.Id;
            user.AITokenLimit = plan.AITokenLimit;
        }
        
        await userManager.UpdateAsync(user);
    }
}

async Task HandleSubscriptionCancellation(
    string? email, 
    UserManager<NexusUser> userManager,
    AppDbContext dbContext)
{
    if (string.IsNullOrEmpty(email)) return;

    var user = await userManager.FindByEmailAsync(email);
    if (user != null)
    {
        var freePlan = await dbContext.SubscriptionPlans.FindAsync(SubscriptionPlan.FreeId);
        user.SubscriptionPlanId = SubscriptionPlan.FreeId;
        user.AITokenLimit = freePlan?.AITokenLimit ?? 5000;
        await userManager.UpdateAsync(user);
    }
}

app.MapGroup("/identity").MapIdentityApi<NexusUser>();

app.MapGet("/identity/login/google", (string? returnUrl) =>
{
    var properties = new AuthenticationProperties
    {
        RedirectUri = "/identity/callback/google",
        Items = { { "returnUrl", string.IsNullOrEmpty(returnUrl) ? "/" : returnUrl } }
    };
    return Results.Challenge(properties, new[] { "Google" });
});

app.MapGet("/identity/callback/google", async (
    HttpContext context, 
    SignInManager<NexusUser> signInManager, 
    UserManager<NexusUser> userManager,
    ILogger<Program> logger) =>
{
    var info = await signInManager.GetExternalLoginInfoAsync();
    if (info == null) 
    {
        logger.LogWarning("External login info from Google is null.");
        return Results.Redirect("/account/login?error=ExternalLoginFailed");
    }

    var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
    if (result.Succeeded)
    {
        logger.LogInformation("User logged in via Google: {Email}", info.Principal.FindFirstValue(ClaimTypes.Email));
        return Results.Redirect("/");
    }

    if (result.IsLockedOut)
    {
        logger.LogWarning("User account locked out during Google login: {Email}", info.Principal.FindFirstValue(ClaimTypes.Email));
        return Results.Redirect("/account/login?error=LockedOut");
    }

    // New user provisioning (blocked when registration is disabled)
    if (!allowRegistration)
    {
        logger.LogWarning("Google provisioning blocked: registration is disabled in this environment.");
        return Results.Redirect("/account/login?error=RegistrationDisabled");
    }

    var email = info.Principal.FindFirstValue(ClaimTypes.Email);
    if (email != null)
    {
        var user = new NexusUser { UserName = email, Email = email, EmailConfirmed = true };
        var createResult = await userManager.CreateAsync(user);
        
        if (createResult.Succeeded)
        {
            await userManager.AddLoginAsync(user, info);
            await signInManager.SignInAsync(user, isPersistent: false);
            logger.LogInformation("New user provisioned via Google: {Email}", email);
            return Results.Redirect("/");
        }

        // Log specific errors
        foreach (var error in createResult.Errors)
        {
            logger.LogError("Google provisioning failed for {Email}: {Code} - {Description}", email, error.Code, error.Description);
        }

        if (createResult.Errors.Any(e => e.Code == "DuplicateEmail" || e.Code == "DuplicateUserName"))
        {
            return Results.Redirect("/account/login?error=UserAlreadyExists");
        }
    }

    logger.LogError("Google provisioning failed - unknown reason for email {Email}", email);
    return Results.Redirect("/account/login?error=ProvisioningFailed");
});

app.MapPost("/account/login-form", async (
    [FromForm] string email, 
    [FromForm] string password, 
    [FromForm] bool rememberMe, 
    [FromForm] string? returnUrl,
    SignInManager<NexusUser> signInManager,
    ILogger<Program> logger) =>
{
    var result = await signInManager.PasswordSignInAsync(email, password, rememberMe, lockoutOnFailure: true);
    if (result.Succeeded)
    {
        logger.LogInformation("User logged in: {Email}", email);
        return Results.Redirect(string.IsNullOrEmpty(returnUrl) ? "/" : returnUrl);
    }
    
    var error = result.IsLockedOut ? "LockedOut" : "InvalidCredentials";
    return Results.Redirect($"/account/login?error={error}");
}).DisableAntiforgery(); // Simplified for now, in production use proper antiforgery

app.MapGet("/account/logout-form", async (
    SignInManager<NexusUser> signInManager,
    ILogger<Program> logger) =>
{
    await signInManager.SignOutAsync();
    logger.LogInformation("User logged out via form.");
    return Results.Redirect("/account/login");
});

app.MapGet("/identity/profile", async (ClaimsPrincipal user, IMediator mediator) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    if (userId == null) return Results.Unauthorized();

    var result = await mediator.Send(new GetUserProfileQuery(userId));
    if (result.IsFailed) return Results.NotFound(result.Errors.FirstOrDefault()?.Message);

    return Results.Ok(result.Value);
}).RequireAuthorization();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(NexusReader.UI.Shared.Services.IKnowledgeGraphService).Assembly);

app.Run();

async Task TriggerBackgroundProcessingForUnindexedBooksAsync(IServiceProvider services)
{
    var logger = services.GetRequiredService<ILogger<Program>>();
    try
    {
        var dbContextFactory = services.GetRequiredService<IDbContextFactory<NexusReader.Data.Persistence.AppDbContext>>();
        using var dbContext = await dbContextFactory.CreateDbContextAsync();
        
        var unindexedEbooks = await dbContext.Ebooks
            .Where(e => !e.IsReadyForReading)
            .ToListAsync();
            
        if (unindexedEbooks.Any())
        {
            logger.LogInformation("[Startup] Found {Count} un-indexed ebooks. Triggering background processing...", unindexedEbooks.Count);
            
            foreach (var ebook in unindexedEbooks)
            {
                logger.LogInformation("[Startup] Queuing background processing for ebook: '{Title}' ({Id})", ebook.Title, ebook.Id);
                
                _ = Task.Run(async () =>
                {
                    try
                    {
                        using var scope = services.CreateScope();
                        var scopedMediator = scope.ServiceProvider.GetRequiredService<IMediator>();
                        await scopedMediator.Send(new ProcessEbookCommand(ebook.Id, ebook.UserId, ebook.TenantId));
                    }
                    catch (Exception ex)
                    {
                        using var scope = services.CreateScope();
                        var scopedLogger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
                        scopedLogger.LogError(ex, "Failed to run background processing for ebook {EbookId} on startup", ebook.Id);
                    }
                });
            }
        }
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Error checking or triggering background processing for unindexed books on startup.");
    }
}

public record KnowledgeRequest(string Text, Guid? EbookId = null);
public record GroundednessRequest(string Answer, string Context);
public record SemanticSearchRequest(string QueryText, int Limit = 5);
public record AskQuestionRequest(string Question, Guid? EbookId = null, int Limit = 5);
