using OpenIddict.Abstractions;
using Ablera.Serdica.Authority.Contracts;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;

using OpenIddict.Server;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using System;
using Microsoft.AspNetCore.Http;

using System.Linq;
using Ablera.Serdica.Authority.Extensions;

using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;

namespace Ablera.Serdica.Authority.OpenIddictServerHandlers;

public sealed class PasswordGrantHandler(
    IUserManagingDirector<IdentityUser<string>> users, 
    IHttpContextAccessor httpContextAccessor) :
        IOpenIddictServerHandler<HandleTokenRequestContext>
{
    public async ValueTask HandleAsync(HandleTokenRequestContext ctx)
    {
        if (!ctx.Request.IsPasswordGrantType())
            return;                               // not our grant → ignore

        var username = ctx.Request.Username;
        var password = ctx.Request.Password;
        if (username is null || password is null)
        {
            ctx.Reject(
             error: Errors.InvalidGrant,
             description: "Missing username or password.");
            return;
        }

        // 1) Find user.
        var user = await users.FindByEmailAsync(username) ??
                   await users.FindByNameAsync(username);
        if (user is null)
        {
            ctx.Reject(
                error: Errors.InvalidGrant,
                description: "Invalid credentials.");
            return;
        }

        // 2) Validate the password.
        var auth = await users.AuthenticateAsync(user, password, false);
        if (!auth.Succeeded || auth.ClaimsPrincipal is null)
        {
            ctx.Reject(
                error: Errors.InvalidGrant,
                description: "Invalid credentials.");
            return;
        }


        var props = new AuthenticationProperties
        {
            IsPersistent = true
        };
        var roleClaims = await users.GetRolesClaimsAsync(user);
        var baseClaims = await users.GetBaseClaimsAsync(user);
        var principal = SerdicaPrincipalBuilder.Build(
            [ ..baseClaims, ..(roleClaims ?? [])], 
            ctx.Request.GetScopes(),
            auth.ClaimsPrincipal.Identity!.AuthenticationType!);

        // Issue the local session cookie **for the browser**
        await httpContextAccessor.HttpContext!.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                principal,
                props);


        // 4) Tell OpenIddict that everything is OK.
        ctx.SignIn(principal);

        // ------------------------------------------------------------------
        var confirmUrl = httpContextAccessor.HttpContext?.Request?.Query.TryGetValue("confirmUrl", out var values) == true
            ? values.FirstOrDefault()
            : null;
        if (string.IsNullOrEmpty(confirmUrl)) return; 

        httpContextAccessor.HttpContext!.Response.Redirect(Uri.UnescapeDataString(confirmUrl));
        ctx.HandleRequest();
    }
}
