using MemberCenter.Api.Extensions;
using MemberCenter.Application.Abstractions;
using MemberCenter.Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System.Text.Json;
using System.Web;

namespace MemberCenter.Api.Controllers;

[ApiController]
public class OAuthController : ControllerBase
{
    private const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
    private readonly string _webLoginUrl;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IOpenIddictApplicationManager _applicationManager;
    private readonly IAuthResourceRegistryService _authResourceRegistry;

    public OAuthController(
        IConfiguration configuration,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IOpenIddictApplicationManager applicationManager,
        IAuthResourceRegistryService authResourceRegistry)
    {
        _webLoginUrl = configuration["Auth:WebLoginUrl"] ?? "/account/login";
        _userManager = userManager;
        _signInManager = signInManager;
        _applicationManager = applicationManager;
        _authResourceRegistry = authResourceRegistry;
    }

    [HttpGet("/oauth/authorize")]
    public async Task<IActionResult> Authorize()
    {
        var request = HttpContext.Features.Get<OpenIddictServerAspNetCoreFeature>()?.Transaction?.Request;
        if (request is null)
        {
            return BadRequest("Invalid OpenIddict request.");
        }

        var cookie = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
        if (!cookie.Succeeded || cookie.Principal is null)
        {
            return Redirect(BuildLoginRedirectUrl());
        }

        var user = await _userManager.GetUserAsync(cookie.Principal);
        if (user is null)
        {
            return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        if (user.DisabledAt.HasValue)
        {
            return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        var application = string.IsNullOrWhiteSpace(request.ClientId)
            ? null
            : await _applicationManager.FindByClientIdAsync(request.ClientId);
        if (application is null)
        {
            return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        var properties = await _applicationManager.GetPropertiesAsync(application);
        var usage = properties.TryGetValue("usage", out var usageValue)
            ? usageValue.ValueKind == JsonValueKind.String ? usageValue.GetString() : usageValue.ToString()
            : null;
        if (string.IsNullOrWhiteSpace(usage))
        {
            return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        var scopes = await _authResourceRegistry.GetAllowedScopesForUsageAsync(usage, HttpContext.RequestAborted);

        var principal = await _signInManager.CreateUserPrincipalAsync(user);
        principal.SetClaim(OpenIddictConstants.Claims.Subject, user.Id.ToString());
        if (!string.IsNullOrWhiteSpace(user.SecurityStamp))
        {
            principal.SetClaim(SecurityStampClaimType, user.SecurityStamp);
        }
        principal.SetScopes(scopes);
        principal.SetResources(await _authResourceRegistry.ResolveAudiencesAsync(scopes, HttpContext.RequestAborted));
        foreach (var claim in principal.Claims)
        {
            claim.SetDestinations(ClaimsExtensions.GetDestinations(claim));
        }

        return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    }

    private string BuildLoginRedirectUrl()
    {
        var returnUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}{Request.QueryString}";
        var separator = _webLoginUrl.Contains('?', StringComparison.Ordinal) ? '&' : '?';
        return $"{_webLoginUrl}{separator}returnUrl={HttpUtility.UrlEncode(returnUrl)}";
    }
}
