using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace StellaOps.Auth.Abstractions;

/// <summary>
/// Factory helpers for returning RFC 7807 problem responses using StellaOps conventions.
/// </summary>
public static class StellaOpsProblemResultFactory
{
    private const string ProblemBase = "https://docs.stella-ops.org/problems";

    /// <summary>
    /// Produces a 401 problem response indicating authentication is required.
    /// </summary>
    public static ProblemHttpResult AuthenticationRequired(string? detail = null, string? instance = null)
        => Create(
            StatusCodes.Status401Unauthorized,
            $"{ProblemBase}/authentication-required",
            "Authentication required",
            detail ?? "Authentication is required to access this resource.",
            instance,
            "unauthorized");

    /// <summary>
    /// Produces a 401 problem response for invalid, expired, or revoked tokens.
    /// </summary>
    public static ProblemHttpResult InvalidToken(string? detail = null, string? instance = null)
        => Create(
            StatusCodes.Status401Unauthorized,
            $"{ProblemBase}/invalid-token",
            "Invalid token",
            detail ?? "The supplied access token is invalid, expired, or revoked.",
            instance,
            "invalid_token");

    /// <summary>
    /// Produces a 403 problem response when access is denied.
    /// </summary>
    public static ProblemHttpResult Forbidden(string? detail = null, string? instance = null)
        => Create(
            StatusCodes.Status403Forbidden,
            $"{ProblemBase}/forbidden",
            "Forbidden",
            detail ?? "The authenticated principal is not authorised to access this resource.",
            instance,
            "forbidden");

    /// <summary>
    /// Produces a 403 problem response for insufficient scopes.
    /// </summary>
    public static ProblemHttpResult InsufficientScope(
        IReadOnlyCollection<string> requiredScopes,
        IReadOnlyCollection<string>? grantedScopes = null,
        string? instance = null)
    {
        ArgumentNullException.ThrowIfNull(requiredScopes);

        var extensions = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
        {
            ["required_scopes"] = requiredScopes.ToArray()
        };

        if (grantedScopes is not null)
        {
            extensions["granted_scopes"] = grantedScopes.ToArray();
        }

        return Create(
            StatusCodes.Status403Forbidden,
            $"{ProblemBase}/insufficient-scope",
            "Insufficient scope",
            "The authenticated principal does not hold the scopes required by this resource.",
            instance,
            "insufficient_scope",
            extensions);
    }

    private static ProblemHttpResult Create(
        int status,
        string type,
        string title,
        string detail,
        string? instance,
        string error,
        IReadOnlyDictionary<string, object?>? extensions = null)
    {
        var problem = new ProblemDetails
        {
            Status = status,
            Type = type,
            Title = title,
            Detail = detail,
            Instance = instance
        };

        problem.Extensions["error"] = error;
        problem.Extensions["error_description"] = detail;

        if (extensions is not null)
        {
            foreach (var entry in extensions)
            {
                problem.Extensions[entry.Key] = entry.Value;
            }
        }

        return TypedResults.Problem(problem);
    }
}
