using QuantEngine.Web.Components;
using QuantEngine.Infrastructure.Data;
using Microsoft.AspNetCore.Components.Authorization;
using QuantEngine.Web.Infrastructure;
using QuantEngine.Infrastructure.Repositories;
using QuantEngine.Infrastructure.Services;
using QuantEngine.Core.Interfaces;
using QuantEngine.Application.Services;
using QuantEngine.Application.Interfaces;
using System.Text.Json;
using Microsoft.AspNetCore.StaticFiles;
using static QuantEngine.Application.Services.DataCollectionService;
using Serilog;
using QuantEngine.Web.Client.Infrastructure;
using QuantEngine.Web.Client.Services;
using QuantEngine.Web.Endpoints;
using System.Security.Cryptography;
using System.Text;
using QuantEngine.Core.Models;
using Microsoft.AspNetCore.Authentication;
using System.Text.Encodings.Web;
using Microsoft.Extensions.Options;
using MudBlazor.Services;

// Serilog Configuration with Telegram Sink
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.Sink(new TelegramSink("8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0", "-5460205872"))
    .CreateLogger();

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

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

// Authentication and Custom State Provider (Shared client components)
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthentication("QuantAdminScheme")
    .AddScheme<AuthenticationSchemeOptions, QuantAdminAuthHandler>("QuantAdminScheme", _ => { });
builder.Services.AddAuthorization();
builder.Services.AddScoped<LocalStorageService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();

builder.Services.AddMudServices();

// PostgreSQL Dapper Setup
var configuredConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var fallbackConnectionString = "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=CHANGE_ME;Search Path=quantengine;";
var connectionString = string.IsNullOrWhiteSpace(configuredConnectionString) || configuredConnectionString.Contains("Password=;", StringComparison.OrdinalIgnoreCase)
    ? fallbackConnectionString
    : configuredConnectionString;
var configuredDatabase = new Npgsql.NpgsqlConnectionStringBuilder(connectionString).Database;
if (!string.Equals(configuredDatabase, "quantenginedb", StringComparison.OrdinalIgnoreCase))
{
    throw new InvalidOperationException("QuantEngine must use the quantenginedb PostgreSQL database.");
}
builder.Services.AddSingleton<IDbConnectionFactory>(new DbConnectionFactory(connectionString));
builder.Services.AddSingleton<DbMigrator>();
builder.Services.AddScoped<IWorkspaceRepository, WorkspaceRepository>();
builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
builder.Services.AddScoped<IPostgresqlHistorySnapshotReader, PostgresqlHistorySnapshotReader>();
builder.Services.AddScoped<HistoryIngestionService>();

// Collection Pipeline Services (PostgreSQL-backed implementations)
builder.Services.AddScoped<ICollectionRepository, CollectionRepository>();
builder.Services.AddScoped<ITokenCache, PostgresTokenCache>();
builder.Services.AddScoped<IKisApiClient, KisApiClient>();
// Note: DataCollectionService has complex dependencies - will be enabled when DB is ready
// builder.Services.AddScoped<PriceDataNormalizer>();
// builder.Services.AddScoped<SourcePriorityResolver>();
// builder.Services.AddScoped<ICollectionOrchestrator, KisDataCollectionOrchestrator>();
// builder.Services.AddScoped<DataCollectionService>();

// HTTP Client & API Services
builder.Services.AddHttpClient<ApiClient>();
builder.Services.AddScoped<ApiClient>();

var app = builder.Build();
var adminSettings = app.Configuration.GetSection("AdminSettings");
var adminUsername = adminSettings["Username"] ?? "admin";
var adminPassword = adminSettings["Password"] ?? string.Empty;

// Initialize database tables (PostgreSQL-backed repositories)
using (var scope = app.Services.CreateScope())
{
    var migrator = scope.ServiceProvider.GetRequiredService<DbMigrator>();
    var tokenCache = scope.ServiceProvider.GetRequiredService<ITokenCache>();
    var collectionRepo = scope.ServiceProvider.GetRequiredService<ICollectionRepository>();
    var workspaceRepo = scope.ServiceProvider.GetRequiredService<IWorkspaceRepository>();

    try
    {
        migrator.Migrate();
        // Ensure tables exist on startup
        await tokenCache.GetCachedTokenAsync("_init_test_");
        await collectionRepo.GetDashboardStateAsync();
        await workspaceRepo.GetAccountsAsync();
        Log.Information("Database tables initialized successfully");
    }
    catch (Exception ex)
    {
        Log.Warning($"Database initialization warning: {ex.Message}");
    }
}

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}
// Redirect status code pages only for non-API routes
app.UseStatusCodePages(async ctx =>
{
    if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api"))
        ctx.HttpContext.Response.Redirect("/not-found");
});

app.UseHttpsRedirection();

// Configure static file MIME types for Blazor
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".wasm"] = "application/wasm";
provider.Mappings[".js"] = "application/javascript";
provider.Mappings[".mjs"] = "application/javascript";
provider.Mappings[".json"] = "application/json";
provider.Mappings[".svg"] = "image/svg+xml";
provider.Mappings[".woff"] = "font/woff";
provider.Mappings[".woff2"] = "font/woff2";

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider,
    ServeUnknownFileTypes = true,
    DefaultContentType = "application/octet-stream"
});

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

app.MapStaticAssets();

app.MapGet("/", () => Results.Redirect("/login"));

// Collection API Endpoints (must be before MapRazorComponents)
app.MapCollectionEndpoints();

// Login API (API-First for Blazor WASM client authentication)
app.MapPost("/api/auth/login", async (JsonElement payload, IWorkspaceRepository workspaceRepo) =>
{
    static string? ReadString(JsonElement root, params string[] names)
    {
        foreach (var name in names)
        {
            if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(name, out var property) && property.ValueKind == JsonValueKind.String)
            {
                return property.GetString();
            }
        }

        return null;
    }

    var username = ReadString(payload, "Username", "username");
    var password = ReadString(payload, "Password", "password");

    if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
    {
        return Results.BadRequest(new { success = false, error = "missing_credentials" });
    }

    WorkspaceAccount? account = null;
    try
    {
        account = await workspaceRepo.GetAccountByUsernameAsync(username.Trim());
    }
    catch (Exception dbEx)
    {
        // Database fallback for development: allow admin:admin
        Console.WriteLine($"[Login] Database lookup failed: {dbEx.Message}");
        if (string.Equals(username, "admin", StringComparison.OrdinalIgnoreCase) && string.Equals(password, "admin"))
        {
            var devToken = Guid.NewGuid().ToString("N");
            var devExpiresAt = DateTimeOffset.UtcNow.AddDays(7);
            return Results.Ok(new
            {
                success = true,
                username = "admin",
                role = "Admin",
                accessToken = devToken,
                expiresAt = devExpiresAt.ToString("O")
            });
        }
        return Results.Json(new { success = false, error = "database_unavailable" }, statusCode: 503);
    }

    if (account is null || !string.Equals(account.IsActive, "true", StringComparison.OrdinalIgnoreCase))
    {
        return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
    }

    var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
    if (!string.Equals(account.PasswordHash, passwordHash, StringComparison.OrdinalIgnoreCase))
    {
        return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
    }

    var rawToken = Guid.NewGuid().ToString("N");
    var tokenHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(rawToken)));
    var now = DateTimeOffset.UtcNow;
    var expiresAt = now.AddDays(7);

    await workspaceRepo.UpsertSessionAsync(new WorkspaceSession
    {
        SessionTokenHash = tokenHash,
        Username = account.Username,
        Role = account.Role,
        CreatedAt = now.ToString("O"),
        ExpiresAt = expiresAt.ToString("O"),
        RevokedAt = null
    });

    return Results.Ok(new
    {
        success = true,
        username = account.Username,
        role = account.Role,
        accessToken = rawToken,
        expiresAt = expiresAt.ToString("O")
    });
}).DisableAntiforgery();

app.MapGet("/api/auth/me", async (HttpContext context, IWorkspaceRepository workspaceRepo) =>
{
    var authHeader = context.Request.Headers.Authorization.ToString();
    if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
    {
        return Results.Unauthorized();
    }

    var token = authHeader["Bearer ".Length..].Trim();
    if (string.IsNullOrWhiteSpace(token))
    {
        return Results.Unauthorized();
    }

    var tokenHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(token)));
    var session = await workspaceRepo.GetSessionByTokenHashAsync(tokenHash);
    if (session is null || !string.IsNullOrWhiteSpace(session.RevokedAt) || DateTimeOffset.TryParse(session.ExpiresAt, out var expiresAt) && expiresAt <= DateTimeOffset.UtcNow)
    {
        return Results.Unauthorized();
    }

    return Results.Ok(new { authenticated = true, username = session.Username, role = session.Role });
});

app.MapPost("/api/auth/logout", async (HttpContext context, IWorkspaceRepository workspaceRepo) =>
{
    var authHeader = context.Request.Headers.Authorization.ToString();
    if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
    {
        return Results.Unauthorized();
    }

    var token = authHeader["Bearer ".Length..].Trim();
    if (string.IsNullOrWhiteSpace(token))
    {
        return Results.Unauthorized();
    }

    var tokenHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(token)));
    await workspaceRepo.RevokeSessionAsync(tokenHash, DateTimeOffset.UtcNow.ToString("O"));
    return Results.Ok(new { success = true });
}).DisableAntiforgery();

app.MapPost("/api/auth/admin/reset-password", async (HttpContext context, JsonElement payload, IWorkspaceRepository workspaceRepo) =>
{
    static string? ReadString(JsonElement root, params string[] names)
    {
        foreach (var name in names)
        {
            if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(name, out var property) && property.ValueKind == JsonValueKind.String)
            {
                return property.GetString();
            }
        }

        return null;
    }

    var username = ReadString(payload, "adminUsername", "AdminUsername", "username", "Username");
    var password = ReadString(payload, "adminPassword", "AdminPassword", "password", "Password");
    var targetUsername = ReadString(payload, "targetUsername", "TargetUsername", "usernameToReset", "UsernameToReset");
    var newPassword = ReadString(payload, "newPassword", "NewPassword");

    if (!string.Equals(username, adminUsername, StringComparison.Ordinal) || !string.Equals(password, adminPassword, StringComparison.Ordinal))
    {
        return Results.Unauthorized();
    }

    if (string.IsNullOrWhiteSpace(targetUsername) || string.IsNullOrWhiteSpace(newPassword))
    {
        return Results.BadRequest(new { success = false, error = "missing_target_or_password" });
    }

    var account = await workspaceRepo.GetAccountByUsernameAsync(targetUsername.Trim());
    if (account is null)
    {
        return Results.NotFound(new { success = false, error = "account_not_found" });
    }

    var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(newPassword)));
    account.PasswordHash = passwordHash;
    account.UpdatedAt = DateTimeOffset.UtcNow.ToString("O");
    var updated = await workspaceRepo.UpsertAccountAsync(account);
    if (!updated)
    {
        return Results.StatusCode(500);
    }

    return Results.Ok(new
    {
        success = true,
        username = account.Username,
        updatedAt = account.UpdatedAt
    });
}).DisableAntiforgery();

// Operational Report serving API (WASM safe file loading substitute)
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
{
    var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
    if (!File.Exists(path))
    {
        return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" });
    }
    var json = await File.ReadAllTextAsync(path);
    using var doc = JsonDocument.Parse(json);
    return Results.Ok(doc.RootElement);
});

app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
{
    var rows = await reader.ReadAsync(domain, limit ?? 500);
    return Results.Ok(new
    {
        formula_id = "POSTGRESQL_HISTORY_SNAPSHOT_API_V1",
        gate = "PASS",
        domain,
        limit = limit ?? 500,
        rows
    });
});

app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload, HistoryIngestionService ingestor) =>
{
    if (payload.ValueKind != JsonValueKind.Object)
    {
        return Results.BadRequest(new { gate = "FAIL", error = "payload_must_be_object" });
    }

    var dict = JsonSerializer.Deserialize<Dictionary<string, object?>>(payload.GetRawText())
              ?? new Dictionary<string, object?>();
    var affected = domain switch
    {
        "decision_result_history" => await ingestor.AppendDecisionAsync(dict),
        "factor_output_history" => await ingestor.AppendFactorOutputAsync(dict),
        "market_raw_history" => await ingestor.AppendMarketRawAsync(dict),
        "market_vs_engine_gap_history" => await ingestor.AppendGapAsync(dict),
        _ => -1
    };
    if (affected < 0)
    {
        return Results.BadRequest(new { gate = "FAIL", error = "unsupported_domain" });
    }
    return Results.Ok(new
    {
        formula_id = "POSTGRESQL_HISTORY_APPEND_API_V1",
        gate = "PASS",
        domain,
        affected
    });
});

app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);

app.Run();

internal sealed class QuantAdminAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public QuantAdminAuthHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        return Task.FromResult(AuthenticateResult.NoResult());
    }

    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    }
}

