using System.Collections.Generic;
using Ablera.Serdica.Authentication.Models.Oidc;
using Ablera.Serdica.Authority.Contracts;
using Ablera.Serdica.Authority.Models;
using Ablera.Serdica.Authority.Services;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;

namespace Ablera.Serdica.Authority.OpenIddictServerHandlers;

public sealed class ClientCredentialsGrantHandler(
   OidcJsonSettingsProvider settingsProvider) :
        IOpenIddictServerHandler<HandleTokenRequestContext>
{
    public async ValueTask HandleAsync(HandleTokenRequestContext ctx)
    {
        if (!ctx.Request.IsClientCredentialsGrantType())
            return;

        var registeredClient = settingsProvider.Settings
            .RegisteredClients
            .FirstOrDefault(x => x.ClientId == ctx.Request.ClientId!);
        if (registeredClient == null)
            return;
        var claims = new List<Claim>
        {
            // Exactly **one** subject claim – the client_id.
            new(Claims.Subject, ctx.Request.ClientId!),
            // Name related claims
            new(ClaimTypes.NameIdentifier, ctx.Request.ClientId!),
            new(ClaimTypes.Name, registeredClient.DisplayName)
        };

        // Any pre-configured claims
        claims.AddRange(
            from claimTypeAndValue in registeredClient.BuiltinClaims ?? [] 
            select new Claim(claimTypeAndValue.Type, claimTypeAndValue.Value));

        // Build a fresh identity to avoid duplicates.
        var principal =
            new ClaimsPrincipal(
                new ClaimsIdentity(
                    claims,
                    OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    Claims.Name,
                    Claims.Role));

        // Scopes: intersect requested with allowed set.
        var scopes = (registeredClient.Permissions ?? [])
            .Where(x => x.StartsWith("scp:"))
            .Select(x => x.Substring(4))
            .Concat(settingsProvider.Settings.Scopes)
            .Distinct()
            .ToArray();
        principal.SetScopes(ctx.Request.GetScopes().Intersect(scopes));
        
        // API audience(s) your APIs expect.
        principal.SetResources(Authentication.Constants.ConstantsClass.SerdicaAPIAudience);
        principal.SetDestinations(c =>
            c.Type == Claims.Name ? new[] { Destinations.AccessToken,
                    Destinations.IdentityToken }
                : new[] { Destinations.AccessToken });

        ctx.SignIn(principal);
    }
}
