using System.Security.Claims;
using ChessCubing.Server.Auth;
using ChessCubing.Server.Data;
using ChessCubing.Server.Users;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOptions<KeycloakAuthOptions>()
    .Configure<IConfiguration>((options, configuration) =>
    {
        options.BaseUrl = configuration["KEYCLOAK_BASE_URL"] ?? options.BaseUrl;
        options.Realm = configuration["KEYCLOAK_REALM"] ?? options.Realm;
        options.ClientId = configuration["KEYCLOAK_CLIENT_ID"] ?? options.ClientId;
        options.AdminRealm = configuration["KEYCLOAK_ADMIN_REALM"] ?? options.AdminRealm;
        options.AdminClientId = configuration["KEYCLOAK_ADMIN_CLIENT_ID"] ?? options.AdminClientId;
        options.AdminUsername = configuration["KEYCLOAK_ADMIN_USERNAME"] ?? options.AdminUsername;
        options.AdminPassword = configuration["KEYCLOAK_ADMIN_PASSWORD"] ?? options.AdminPassword;
    });

builder.Services.AddOptions<SiteDataOptions>()
    .Configure<IConfiguration>((options, configuration) =>
    {
        options.Host = configuration["SITE_DB_HOST"] ?? options.Host;
        options.Database = configuration["SITE_DB_NAME"] ?? options.Database;
        options.Username = configuration["SITE_DB_USER"] ?? options.Username;
        options.Password = configuration["SITE_DB_PASSWORD"] ?? options.Password;

        if (int.TryParse(configuration["SITE_DB_PORT"], out var port) && port > 0)
        {
            options.Port = port;
        }
    });

builder.Services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = "chesscubing.auth";
        options.Cookie.HttpOnly = true;
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        options.SlidingExpiration = true;
        options.ExpireTimeSpan = TimeSpan.FromDays(7);
        options.Events = new CookieAuthenticationEvents
        {
            OnRedirectToLogin = context =>
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return Task.CompletedTask;
            },
            OnRedirectToAccessDenied = context =>
            {
                context.Response.StatusCode = StatusCodes.Status403Forbidden;
                return Task.CompletedTask;
            },
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddHttpClient<KeycloakAuthService>();
builder.Services.AddSingleton<MySqlUserProfileStore>();

var app = builder.Build();

await using (var scope = app.Services.CreateAsyncScope())
{
    var profileStore = scope.ServiceProvider.GetRequiredService<MySqlUserProfileStore>();
    await profileStore.InitializeAsync(CancellationToken.None);
}

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

app.MapGet("/api/health", () => TypedResults.Ok(new { status = "ok" }));

app.MapGet("/api/auth/session", (ClaimsPrincipal user) =>
    TypedResults.Ok(AuthSessionResponse.FromUser(user)));

app.MapGet("/api/users/me", async Task<IResult> (
    ClaimsPrincipal user,
    MySqlUserProfileStore profileStore,
    CancellationToken cancellationToken) =>
{
    var siteUser = AuthenticatedSiteUserFactory.FromClaimsPrincipal(user);
    if (siteUser is null)
    {
        return TypedResults.BadRequest(new ApiErrorResponse("La session utilisateur est incomplete."));
    }

    var profile = await profileStore.GetOrCreateAsync(siteUser, cancellationToken);
    return TypedResults.Ok(profile);
}).RequireAuthorization();

app.MapPut("/api/users/me", async Task<IResult> (
    UpdateUserProfileRequest request,
    ClaimsPrincipal user,
    MySqlUserProfileStore profileStore,
    CancellationToken cancellationToken) =>
{
    var siteUser = AuthenticatedSiteUserFactory.FromClaimsPrincipal(user);
    if (siteUser is null)
    {
        return TypedResults.BadRequest(new ApiErrorResponse("La session utilisateur est incomplete."));
    }

    try
    {
        var profile = await profileStore.UpdateAsync(siteUser, request, cancellationToken);
        return TypedResults.Ok(profile);
    }
    catch (UserProfileValidationException exception)
    {
        return TypedResults.BadRequest(new ApiErrorResponse(exception.Message));
    }
}).RequireAuthorization();

app.MapPost("/api/auth/login", async Task<IResult> (
    LoginRequest request,
    HttpContext httpContext,
    KeycloakAuthService keycloak,
    CancellationToken cancellationToken) =>
{
    if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
    {
        return TypedResults.BadRequest(new ApiErrorResponse("Nom d'utilisateur et mot de passe obligatoires."));
    }

    try
    {
        var userInfo = await keycloak.LoginAsync(request.Username.Trim(), request.Password, cancellationToken);
        await SignInAsync(httpContext, userInfo);
        return TypedResults.Ok(AuthSessionResponse.FromUser(httpContext.User));
    }
    catch (KeycloakAuthException exception)
    {
        return TypedResults.Json(new ApiErrorResponse(exception.Message), statusCode: exception.StatusCode);
    }
});

app.MapPost("/api/auth/register", async Task<IResult> (
    RegisterRequest request,
    HttpContext httpContext,
    KeycloakAuthService keycloak,
    CancellationToken cancellationToken) =>
{
    if (string.IsNullOrWhiteSpace(request.Username) ||
        string.IsNullOrWhiteSpace(request.Email) ||
        string.IsNullOrWhiteSpace(request.Password))
    {
        return TypedResults.BadRequest(new ApiErrorResponse("Nom d'utilisateur, email et mot de passe obligatoires."));
    }

    if (!string.Equals(request.Password, request.ConfirmPassword, StringComparison.Ordinal))
    {
        return TypedResults.BadRequest(new ApiErrorResponse("Les mots de passe ne correspondent pas."));
    }

    try
    {
        var userInfo = await keycloak.RegisterAsync(request with
        {
            Username = request.Username.Trim(),
            Email = request.Email.Trim(),
            FirstName = request.FirstName?.Trim(),
            LastName = request.LastName?.Trim(),
        }, cancellationToken);

        await SignInAsync(httpContext, userInfo);
        return TypedResults.Ok(AuthSessionResponse.FromUser(httpContext.User));
    }
    catch (KeycloakAuthException exception)
    {
        return TypedResults.Json(new ApiErrorResponse(exception.Message), statusCode: exception.StatusCode);
    }
});

app.MapPost("/api/auth/logout", async Task<IResult> (HttpContext httpContext) =>
{
    await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return TypedResults.Ok(AuthSessionResponse.FromUser(new ClaimsPrincipal(new ClaimsIdentity())));
});

app.Run();

static async Task SignInAsync(HttpContext httpContext, KeycloakUserInfo userInfo)
{
    var claims = new List<Claim>();

    if (!string.IsNullOrWhiteSpace(userInfo.Subject))
    {
        claims.Add(new Claim("sub", userInfo.Subject));
        claims.Add(new Claim(ClaimTypes.NameIdentifier, userInfo.Subject));
    }

    if (!string.IsNullOrWhiteSpace(userInfo.PreferredUsername))
    {
        claims.Add(new Claim("preferred_username", userInfo.PreferredUsername));
        claims.Add(new Claim(ClaimTypes.Name, userInfo.PreferredUsername));
    }

    if (!string.IsNullOrWhiteSpace(userInfo.Name))
    {
        claims.Add(new Claim("name", userInfo.Name));
    }

    if (!string.IsNullOrWhiteSpace(userInfo.Email))
    {
        claims.Add(new Claim("email", userInfo.Email));
        claims.Add(new Claim(ClaimTypes.Email, userInfo.Email));
    }

    foreach (var role in userInfo.Roles.Where(role => !string.IsNullOrWhiteSpace(role)))
    {
        claims.Add(new Claim("role", role));
        claims.Add(new Claim(ClaimTypes.Role, role));
    }

    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var principal = new ClaimsPrincipal(identity);

    await httpContext.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme,
        principal,
        new AuthenticationProperties
        {
            IsPersistent = true,
            AllowRefresh = true,
            ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7),
        });

    httpContext.User = principal;
}
