namespace MyOffice.Web.Auth;

using Identity.Domain;
using Identity.Repositories;
using Microsoft.AspNetCore.Identity;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;

public sealed class PasswordGrantHandler : IOpenIddictServerHandler<HandleTokenRequestContext>
{
	private readonly AppUserManager _userManager;
	private readonly SignInManager<ApplicationUser<Guid>> _signInManager;

	public PasswordGrantHandler(
		AppUserManager userManager,
		SignInManager<ApplicationUser<Guid>> signInManager
	)
	{
		_userManager = userManager;
		_signInManager = signInManager;
	}

	public async ValueTask HandleAsync(HandleTokenRequestContext context)
	{
		if (!string.Equals(context.Request.GrantType, GrantTypes.Password, StringComparison.Ordinal))
			return;

		var username = context.Request.Username?.Trim();
		if (string.IsNullOrWhiteSpace(username))
		{
			context.Reject(Errors.InvalidGrant, "Invalid username or password.");
			return;
		}

		var user = await _userManager.FindByNameAsync(username)
			?? await _userManager.FindByEmailAsync(username);
		if (user is null)
		{
			context.Reject(Errors.InvalidGrant, "Invalid username or password.");
			return;
		}

		if (!await _signInManager.CanSignInAsync(user))
		{
			context.Reject(Errors.InvalidGrant, "The specified user cannot sign in.");
			return;
		}

		if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user))
		{
			context.Reject(Errors.InvalidGrant, "The specified user is locked out.");
			return;
		}

		var result = await _signInManager.CheckPasswordSignInAsync(user, context.Request.Password!, lockoutOnFailure: true);
		if (!result.Succeeded)
		{
			context.Reject(Errors.InvalidGrant, "Invalid username or password.");
			return;
		}

		if (_userManager.SupportsUserLockout)
			await _userManager.ResetAccessFailedCountAsync(user);

		context.SignIn(OpenIddictClaimsHelper.CreatePrincipal(user, context.Request.GetScopes()));
	}
}
