using Microsoft.AspNetCore.DataProtection;
using QuantEngine.Infrastructure.Data;
using QuantEngine.Infrastructure.Repositories;
using QuantEngine.Infrastructure.Services;
using QuantEngine.Core.Interfaces;
using QuantEngine.Application.Services;
using QuantEngine.Application.Interfaces;
using Serilog;
using QuantEngine.Web.Services;
using Hangfire;
using Npgsql;
using FastEndpoints;
using FluentValidation;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.File("logs/quantengine-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

// Dapper has no built-in handler for System.DateOnly (params or result mapping) — register once globally.
Dapper.SqlMapper.AddTypeHandler(new DateOnlyTypeHandler());

try
{
    var builder = WebApplication.CreateBuilder(args);
    builder.Host.UseSerilog();

    // Data Protection: without this, ASP.NET Core derives its key-ring
    // discriminator from the app's physical content root path. Every
    // deployment lands in a brand-new directory
    // (~/deployments/quantengine_{tag}_{hash}/), so the discriminator
    // changed on every single deploy and every previously-issued auth
    // cookie became undecryptable -- forcing all users to log in again
    // after each release. SetApplicationName pins a stable discriminator;
    // PersistKeysToFileSystem points at a location outside the versioned
    // deployment folders so the actual key material also survives restarts.
    var dataProtectionKeysPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "quantengine-keys");
    builder.Services.AddDataProtection()
        .SetApplicationName("QuantEngine")
        .PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeysPath));

    // Authentication & Authorization
    builder.Services.AddAuthentication(opts =>
    {
        opts.DefaultAuthenticateScheme = AdminAuthDefaults.Scheme;
        opts.DefaultChallengeScheme = AdminAuthDefaults.Scheme;
    })
    .AddCookie(AdminAuthDefaults.Scheme, opts =>
    {
        opts.Cookie.Name = AdminAuthDefaults.CookieName;
        opts.Cookie.HttpOnly = true;
        opts.Cookie.SameSite = SameSiteMode.Lax;
        opts.Cookie.SecurePolicy = builder.Environment.IsProduction() ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
        opts.LoginPath = "/Account/Login";
        opts.AccessDeniedPath = "/Account/AccessDenied";
        opts.SlidingExpiration = true;
        opts.ExpireTimeSpan = TimeSpan.FromHours(12);
    });

    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy(AdminAuthDefaults.Scheme, policy =>
        {
            policy.RequireAuthenticatedUser();
        });
    });
    builder.Services.AddAntiforgery();

    // Razor Pages with authorization conventions
    builder.Services.AddRazorPages(options =>
    {
        options.Conventions.AuthorizeFolder("/Admin", AdminAuthDefaults.Scheme);
        options.Conventions.AllowAnonymousToPage("/Account/Login");
        options.Conventions.AllowAnonymousToPage("/Account/AccessDenied");
    });

    // Authentication Services
    builder.Services.AddScoped<IIpLockoutService, IpLockoutService>();
    builder.Services.AddScoped<AuthService>();

    // PostgreSQL Dapper Setup
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
        ?? throw new InvalidOperationException("Connection string 'DefaultConnection' is required.");

    var configuredDatabase = new NpgsqlConnectionStringBuilder(connectionString).Database;
    if (!string.Equals(configuredDatabase, "quantenginedb", StringComparison.OrdinalIgnoreCase))
    {
        throw new InvalidOperationException("QuantEngine must use the quantenginedb PostgreSQL database.");
    }

    var dataSource = NpgsqlDataSource.Create(connectionString);
    builder.Services.AddSingleton(dataSource);
    builder.Services.AddSingleton<IDbConnectionFactory>(new DbConnectionFactory(dataSource));
    builder.Services.AddSingleton(sp => new DbMigrator(connectionString, sp.GetRequiredService<ILogger<DbMigrator>>()));

    // Repository Services
    builder.Services.AddScoped<IWorkspaceRepository, WorkspaceRepository>();
    builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
    builder.Services.AddScoped<INormalizedLearningStore, NormalizedLearningStore>();
    builder.Services.AddScoped<ILearningDatasetReader, LearningDatasetReader>();
    builder.Services.AddScoped<DecisionLearningService>();
    builder.Services.AddScoped<LearningDatasetService>();
    builder.Services.AddSingleton<GatherTradingDataParser>();
    builder.Services.AddScoped<JsonSeedIngestionService>();
    builder.Services.AddScoped<IPostgresqlHistorySnapshotReader, PostgresqlHistorySnapshotReader>();
    builder.Services.AddScoped<HistoryIngestionService>();
    builder.Services.AddScoped<CollectionRepository>();
    builder.Services.AddScoped<ICollectionReadRepository>(sp => sp.GetRequiredService<CollectionRepository>());
    builder.Services.AddScoped<ICollectionWriteRepository>(sp => sp.GetRequiredService<CollectionRepository>());
    builder.Services.AddSingleton<ICollectionSchemaInitializer, CollectionSchemaInitializer>();
    builder.Services.AddScoped<ICollectionReadModelService, CollectionReadModelService>();
    builder.Services.AddSingleton<IRuntimeAuditTrailService, RuntimeAuditTrailService>();
    builder.Services.AddScoped<ITokenCache, PostgresTokenCache>();
    builder.Services.AddHttpClient<IKisApiClient, KisApiClient>();

    // Collection Pipeline Services
    builder.Services.AddScoped<SourcePriorityResolver>();
    builder.Services.AddScoped<PriceDataNormalizer>();
    builder.Services.AddScoped<ICollectionOrchestrator, KisDataCollectionOrchestrator>();
    builder.Services.AddScoped<IPriceHistoryReader, PriceHistoryReader>();
    builder.Services.AddOptions<SchedulerServiceOptions>();
    builder.Services.AddHostedService<CollectionBootstrapHostedService>();

    // Hangfire Background Jobs
    try
    {
        var hangfireConnectionString = builder.Configuration.GetConnectionString("HangfireConnection") ?? connectionString;
        builder.Services.AddHangfireServices(hangfireConnectionString);
    }
    catch (Exception ex)
    {
        Log.Warning("Hangfire initialization failed: {Message}", ex.Message);
    }

    // FluentValidation
    builder.Services.AddValidatorsFromAssemblyContaining<QuantEngine.Infrastructure.Repositories.WorkspaceRepository>();

    // FastEndpoints (for API endpoints)
    builder.Services.AddFastEndpoints();

    var app = builder.Build();

    app.UseSerilogRequestLogging();
    app.UseFastEndpoints();

    // Non-blocking Safe Database Initialization & Migration Check
    using (var scope = app.Services.CreateScope())
    {
        var migrator = scope.ServiceProvider.GetRequiredService<DbMigrator>();
        var collectionSchemaInitializer = scope.ServiceProvider.GetRequiredService<ICollectionSchemaInitializer>();

        try
        {
            await collectionSchemaInitializer.InitializeAsync();
            
            // Execute DbUp migrations in isolated safe block
            migrator.Migrate();
            Log.Information("✅ Database schema migration (DbUp) check successful");
        }
        catch (Exception ex)
        {
            // Crucial: DbUp failure should log critical warning but NOT crash the Web Application service startup
            Log.Error(ex, "⚠️ Database migration (DbUp) encounter warning or delay. Service proceeding in fallback readiness state.");
        }
    }

    // Error handling & HSTS
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error", createScopeForErrors: true);
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAntiforgery();

    app.UseAuthentication();
    app.UseAuthorization();

    // Hangfire Dashboard
    try
    {
        app.UseHangfireSetup(app.Services);
    }
    catch (Exception ex)
    {
        Log.Warning("Hangfire setup failed: {Message}", ex.Message);
    }

    // Root redirect: unauthenticated → /Account/Login, authenticated → /Admin/Dashboard
    app.MapGet("/", context =>
    {
        if (context.User?.Identity?.IsAuthenticated ?? false)
            context.Response.Redirect("/Admin/Dashboard");
        else
            context.Response.Redirect("/Account/Login");
        return Task.CompletedTask;
    });

    // Login redirect convenience route
    app.MapGet("/login", context =>
    {
        context.Response.Redirect("/Account/Login", permanent: false);
        return Task.CompletedTask;
    });

    app.MapRazorPages();

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
    throw;
}
finally
{
    Log.CloseAndFlush();
}
