using System.Security.Claims;
using System.Text.Encodings.Web;
using CashLineERP.Application.Identity;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

namespace CashLineERP.Api.Infrastructure;

internal sealed class BearerAccessTokenAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly IAccessTokenService _accessTokenService;

    public BearerAccessTokenAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        IAccessTokenService accessTokenService)
        : base(options, logger, encoder)
    {
        _accessTokenService = accessTokenService;
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("Authorization", out var authorizationValues))
        {
            return Task.FromResult(AuthenticateResult.NoResult());
        }

        var authorizationHeader = authorizationValues.ToString();
        if (!authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
        {
            return Task.FromResult(AuthenticateResult.NoResult());
        }

        var accessToken = authorizationHeader["Bearer ".Length..].Trim();
        var authenticatedUser = _accessTokenService.ValidateToken(accessToken);
        if (authenticatedUser is null)
        {
            return Task.FromResult(AuthenticateResult.Fail("Access token is invalid or expired."));
        }

        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, authenticatedUser.UserId.ToString()),
            new(ClaimTypes.Name, authenticatedUser.LoginName),
            new("display_name", authenticatedUser.DisplayName),
            new(ApiClaimTypes.UserType, authenticatedUser.UserTypeCode)
        };

        if (authenticatedUser.PartyId.HasValue)
        {
            claims.Add(new Claim(ApiClaimTypes.PartyId, authenticatedUser.PartyId.Value.ToString()));
        }

        foreach (var roleCode in authenticatedUser.RoleCodes
                     .Where(roleCode => !string.IsNullOrWhiteSpace(roleCode))
                     .Distinct(StringComparer.OrdinalIgnoreCase))
        {
            claims.Add(new Claim(ClaimTypes.Role, roleCode));
            claims.Add(new Claim(ApiClaimTypes.RoleCode, roleCode));
        }

        foreach (var functionCode in authenticatedUser.FunctionCodes
                     .Where(functionCode => !string.IsNullOrWhiteSpace(functionCode))
                     .Distinct(StringComparer.OrdinalIgnoreCase))
        {
            claims.Add(new Claim(ApiClaimTypes.FunctionCode, functionCode));
        }

        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}
