using GmRelay.Web;
using GmRelay.Web.Components;
using GmRelay.Web.Health;
using GmRelay.Web.Services;
using GmRelay.Web.Services.Portfolio;
using GmRelay.Web.Services.Portfolio.Covers;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using Telegram.Bot;
using Npgsql;

var builder = WebApplication.CreateBuilder(args);

// Add Aspire service defaults
builder.AddServiceDefaults();

// Add HttpClient
builder.Services.AddHttpClient();

// Add HttpContextAccessor for platform-agnostic identity resolution
builder.Services.AddHttpContextAccessor();

// Add health checks
builder.Services.AddHealthChecks()
    .AddCheck<NpgsqlHealthCheck>("npgsql");

// Add Data Protection
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("/app/dataprotection-keys"));

// Add Npgsql
builder.AddNpgsqlDataSource("gmrelaydb");

// Add Services
builder.Services.AddSingleton<TelegramAuthService>();
builder.Services.AddPortfolioCoverStorage(builder.Configuration);
builder.Services.Configure<DiscordOAuthOptions>(builder.Configuration.GetSection("Discord"));
builder.Services.AddSingleton<DiscordAuthService>();
builder.Services.AddSingleton<DiscordOAuthStateStore>();
builder.Services.AddSingleton<ISessionStore, SessionService>();
builder.Services.AddScoped<AuthorizedSessionService>();
builder.Services.AddScoped<AuthorizedMembershipService>();
builder.Services.AddScoped<CalendarSubscriptionService>();
builder.Services.AddSingleton<IPortfolioStore, PortfolioService>();
builder.Services.AddScoped<AuthorizedPortfolioService>();

// Add Bot Client
builder.Services.AddSingleton<ITelegramBotClient>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    var token = config["Telegram__BotToken"] ?? config["Telegram:BotToken"]
        ?? throw new InvalidOperationException("Telegram__BotToken is required.");
    return new TelegramBotClient(token);
});

// Add Authentication with hardened cookie settings
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/login";
        options.AccessDeniedPath = "/access-denied";
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.ExpireTimeSpan = TimeSpan.FromDays(7);
        options.SlidingExpiration = true;
    });

builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();

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

var app = builder.Build();

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

app.UseHttpsRedirection();

// Security headers middleware
app.Use(async (context, next) =>
{
    context.Response.Headers["X-Content-Type-Options"] = "nosniff";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
    context.Response.Headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()";
    await next();
});

app.UsePortfolioCoverFiles();

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

app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

// Health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        var response = new
        {
            status = report.Status == HealthStatus.Healthy ? "healthy" : "unhealthy",
            timestamp = DateTimeOffset.UtcNow.ToString("O")
        };
        await context.Response.WriteAsJsonAsync(response);
    }
});

app.MapHealthChecks("/alive", new HealthCheckOptions
{
    Predicate = r => r.Tags.Contains("live")
});

// Endpoint to handle Telegram Login callback
app.MapGet("/auth/telegram", async (HttpContext context, TelegramAuthService authService, ISessionStore sessionStore) =>
{
    if (!authService.Verify(context.Request.Query, out var telegramId, out var name))
        return Results.Redirect("/login?error=auth_failed");

    await sessionStore.UpsertPlayerAsync("Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture), name, null);

    // If already authenticated via another platform, link instead of replacing session
    if (context.User.Identity?.IsAuthenticated == true
        && context.User.TryGetPlatformIdentity(out var currentPlatform, out var currentExternalUserId)
        && currentPlatform != "Telegram")
    {
        try
        {
            // Always make Telegram the primary (it has the historical data/groups)
            await sessionStore.LinkIdentityAsync(
                "Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture),
                currentPlatform, currentExternalUserId,
                name);
            return Results.Redirect("/profile?linked=telegram");
        }
        catch (InvalidOperationException ex)
        {
            return Results.Redirect($"/profile?link_error={Uri.EscapeDataString(ex.Message)}");
        }
    }

    var authProperties = new AuthenticationProperties { IsPersistent = true };
    await context.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme,
        CreateTelegramPrincipal(telegramId, name),
        authProperties);
    return Results.Redirect("/");
});

app.MapPost("/auth/telegram-webapp", async (
    HttpContext context,
    TelegramAuthService authService,
    ISessionStore sessionStore,
    TelegramWebAppAuthRequest request) =>
{
    if (!authService.VerifyWebAppInitData(request.InitData, out var telegramId, out var name))
    {
        return Results.Unauthorized();
    }

    await sessionStore.UpsertPlayerAsync("Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture), name, null);

    var authProperties = new AuthenticationProperties { IsPersistent = true };
    await context.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme,
        CreateTelegramPrincipal(telegramId, name),
        authProperties);

    return Results.Ok(new { redirectUrl = "/" });
}).DisableAntiforgery();

app.MapPost("/auth/telegram-login", async (
    HttpContext context,
    TelegramAuthService authService,
    ISessionStore sessionStore,
    TelegramLoginPayload request) =>
{
    if (!authService.VerifyLoginPayload(request, out var telegramId, out var name))
    {
        return Results.Unauthorized();
    }

    await sessionStore.UpsertPlayerAsync("Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture), name, null);

    var authProperties = new AuthenticationProperties { IsPersistent = true };
    await context.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme,
        CreateTelegramPrincipal(telegramId, name),
        authProperties);

    return Results.Ok(new { redirectUrl = "/" });
}).DisableAntiforgery();

app.MapGet("/auth/status", (HttpContext context) =>
    Results.Ok(new { authenticated = context.User.Identity?.IsAuthenticated == true }));

app.MapPost("/auth/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Results.Redirect("/");
});

// Discord OAuth endpoints
app.MapGet("/auth/discord", (DiscordAuthService discordAuth, DiscordOAuthStateStore stateStore) =>
{
    var state = stateStore.CreateState();
    var url = discordAuth.BuildAuthorizeUrl(state);
    return Results.Redirect(url);
});

app.MapGet("/auth/discord/callback", async (
    HttpContext context,
    DiscordAuthService discordAuth,
    DiscordOAuthStateStore stateStore,
    ISessionStore sessionStore) =>
{
    var code = context.Request.Query["code"].ToString();
    var state = context.Request.Query["state"].ToString();

    if (string.IsNullOrWhiteSpace(code) ||
        string.IsNullOrWhiteSpace(state) ||
        !stateStore.ValidateAndRemove(state))
    {
        return Results.Redirect("/login?error=auth_failed");
    }

    var user = await discordAuth.ExchangeCodeAsync(code);
    if (user is null)
        return Results.Redirect("/login?error=auth_failed");

    await sessionStore.UpsertDiscordUserAsync(user.Id, user.DisplayName, user.AvatarUrl);

    // If already authenticated via another platform, link instead of replacing session
    if (context.User.Identity?.IsAuthenticated == true
        && context.User.TryGetPlatformIdentity(out var currentPlatform, out var currentExternalUserId)
        && currentPlatform != "Discord")
    {
        try
        {
            await sessionStore.LinkIdentityAsync(
                currentPlatform, currentExternalUserId,
                "Discord", user.Id,
                user.DisplayName);
            return Results.Redirect("/profile?linked=discord");
        }
        catch (InvalidOperationException ex)
        {
            return Results.Redirect($"/profile?link_error={Uri.EscapeDataString(ex.Message)}");
        }
    }

    var authProperties = new AuthenticationProperties { IsPersistent = true };
    await context.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme,
        CreateDiscordPrincipal(user.Id, user.DisplayName, user.AvatarUrl),
        authProperties);

    return Results.Redirect("/");
});

// Identity linking API endpoints
app.MapGet("/api/me/identities", async (
    HttpContext context,
    ISessionStore sessionStore) =>
{
    if (!context.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
        return Results.Unauthorized();

    var identities = await sessionStore.GetLinkedIdentitiesAsync(platform, externalUserId);
    return Results.Ok(identities);
}).RequireAuthorization();

app.MapDelete("/api/me/identities/{targetPlatform}/{targetExternalUserId}", async (
    HttpContext context,
    ISessionStore sessionStore,
    string targetPlatform,
    string targetExternalUserId) =>
{
    if (!context.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
        return Results.Unauthorized();

    try
    {
        await sessionStore.UnlinkIdentityAsync(platform, externalUserId, targetPlatform, targetExternalUserId);
        return Results.NoContent();
    }
    catch (InvalidOperationException ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
}).RequireAuthorization();

// Public calendar subscription endpoint (no auth required)
app.MapGet("/calendar/{token}.ics", async (
    string token,
    CalendarSubscriptionService service,
    CancellationToken ct) =>
{
    try
    {
        var ics = await service.GetIcsAsync(token, ct);
        var bytes = System.Text.Encoding.UTF8.GetBytes(ics);
        return Results.File(bytes, "text/calendar", "schedule.ics");
    }
    catch (SubscriptionNotFoundException)
    {
        return Results.NotFound();
    }
});

app.Run();

static ClaimsPrincipal CreateTelegramPrincipal(long telegramId, string name)
{
    var claims = new List<Claim>
    {
        new(ClaimTypes.NameIdentifier, telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture)),
        new(ClaimTypes.Name, name),
        new("TelegramId", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture)),
        new("Platform", "Telegram")
    };

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    return new ClaimsPrincipal(claimsIdentity);
}

static ClaimsPrincipal CreateDiscordPrincipal(string discordId, string name, string? avatarUrl)
{
    var claims = new List<Claim>
    {
        new(ClaimTypes.NameIdentifier, discordId),
        new(ClaimTypes.Name, name),
        new("DiscordId", discordId),
        new("Platform", "Discord")
    };

    if (!string.IsNullOrWhiteSpace(avatarUrl))
        claims.Add(new Claim("AvatarUrl", avatarUrl));

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    return new ClaimsPrincipal(claimsIdentity);
}

public sealed record TelegramWebAppAuthRequest(string InitData);
