using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Hua.Todo.Application.Data;

namespace Hua.Todo.Application.CloudSync.Auth;

/// <summary>
/// 基于服务端会话表的 Bearer Token 鉴权处理器。
/// </summary>
public class SessionAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly TodoDbContext _dbContext;
    private readonly IRolePermissionMapper _rolePermissionMapper;

    /// <summary>
    /// 创建 <see cref="SessionAuthenticationHandler"/>。
    /// </summary>
    /// <param name="options">鉴权 scheme 选项。</param>
    /// <param name="logger">日志。</param>
    /// <param name="encoder">编码器。</param>
    /// <param name="dbContext">数据库上下文。</param>
    /// <param name="rolePermissionMapper">角色权限映射器。</param>
    public SessionAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        TodoDbContext dbContext,
        IRolePermissionMapper rolePermissionMapper)
        : base(options, logger, encoder)
    {
        _dbContext = dbContext;
        _rolePermissionMapper = rolePermissionMapper;
    }

    /// <inheritdoc />
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authorization = Request.Headers.Authorization.ToString();
        if (string.IsNullOrWhiteSpace(authorization))
        {
            return AuthenticateResult.NoResult();
        }

        if (!authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
        {
            return AuthenticateResult.NoResult();
        }

        var token = authorization["Bearer ".Length..].Trim();
        if (!Guid.TryParse(token, out var sessionId))
        {
            return AuthenticateResult.Fail("Invalid bearer token format.");
        }

        var now = DateTime.UtcNow;

        var session = await _dbContext.UserSessions
            .AsNoTracking()
            .Include(s => s.User)
            .FirstOrDefaultAsync(s => s.Id == sessionId, Context.RequestAborted);

        if (session == null || session.User == null)
        {
            return AuthenticateResult.Fail("Session not found.");
        }

        if (session.ExpiresAtUtc <= now)
        {
            return AuthenticateResult.Fail("Session expired.");
        }

        var user = session.User;
        var permissions = _rolePermissionMapper.GetPermissions(user.Role).ToList();

        var allowSync = await _dbContext.SecurityPolicies
            .AsNoTracking()
            .Where(p => p.UserId == user.Id)
            .Select(p => (bool?)p.AllowSync)
            .FirstOrDefaultAsync(Context.RequestAborted);

        if (allowSync.HasValue && !allowSync.Value)
        {
            permissions.RemoveAll(p => string.Equals(p, CloudPermissions.SyncWrite, StringComparison.OrdinalIgnoreCase));
        }

        var claims = new List<Claim>
        {
            new(ClaimTypes.Sid, session.Id.ToString()),
            new(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new(ClaimTypes.Name, user.UserName),
            new(ClaimTypes.Role, user.Role)
        };

        foreach (var p in permissions)
        {
            claims.Add(new Claim(CloudClaims.Permission, p));
        }

        if (session.StepUpExpiresAtUtc.HasValue && session.StepUpExpiresAtUtc.Value > now)
        {
            claims.Add(new Claim(CloudClaims.StepUp, "true"));
        }

        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return AuthenticateResult.Success(ticket);
    }
}
