using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Tiku.Application.Security;
using Tiku.Domain.Operations;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Api.Security;

internal sealed class AuditingAuthorizationMiddlewareResultHandler(
    IServiceScopeFactory scopeFactory,
    ILogger<AuditingAuthorizationMiddlewareResultHandler> logger) : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler fallback = new();

    public async Task HandleAsync(
        RequestDelegate next,
        HttpContext context,
        AuthorizationPolicy policy,
        PolicyAuthorizationResult authorizeResult)
    {
        if (authorizeResult.Forbidden && context.User.Identity?.IsAuthenticated == true)
        {
            try
            {
                await using var scope = scopeFactory.CreateAsyncScope();
                var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
                dbContext.AuditLogs.Add(new AuditLog
                {
                    TenantId = Guid.TryParse(context.User.FindFirst(TikuClaimTypes.TenantId)?.Value, out var tenantId)
                        ? tenantId
                        : null,
                    ActorUserId = Guid.TryParse(context.User.FindFirst(TikuClaimTypes.UserId)?.Value, out var userId)
                        ? userId
                        : null,
                    Action = "authorization.access_denied",
                    TargetType = "http_endpoint",
                    TargetId = context.Request.Path,
                    IpAddress = context.Connection.RemoteIpAddress?.ToString(),
                    UserAgent = context.Request.Headers.UserAgent.ToString(),
                    Details = JsonSerializer.SerializeToElement(new
                    {
                        Method = context.Request.Method,
                        Path = context.Request.Path.Value,
                        Realm = context.User.FindFirst(TikuClaimTypes.Realm)?.Value,
                        Failure = authorizeResult.AuthorizationFailure?.FailureReasons.Select(reason => reason.Message).ToArray()
                    })
                });
                await dbContext.SaveChangesAsync(context.RequestAborted);
            }
            catch (Exception exception)
            {
                logger.LogWarning(exception, "Failed to persist authorization denial audit event.");
            }
        }

        await fallback.HandleAsync(next, context, policy, authorizeResult);
    }
}
