using Microsoft.AspNetCore.Mvc;

namespace Tiku.Api.Middleware;

internal sealed class ExceptionHandlingMiddleware(
    RequestDelegate next,
    ILogger<ExceptionHandlingMiddleware> logger,
    IHostEnvironment environment,
    IEnumerable<IExceptionProblemDetailsMapper> mappers)
{
    private readonly IReadOnlyCollection<IExceptionProblemDetailsMapper> problemDetailsMappers = mappers.ToArray();

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception exception)
        {
            var mappings = problemDetailsMappers
                .Select(mapper => mapper.TryMap(exception, out var mapping) ? mapping : null)
                .Where(mapping => mapping is not null)
                .Cast<ExceptionProblemDetailsMapping>()
                .ToArray();
            if (mappings.Length == 1)
            {
                await WriteProblemAsync(context, mappings[0]);
                return;
            }

            if (mappings.Length > 1)
                logger.LogCritical(exception, "Multiple ProblemDetails mappers matched {ExceptionType}",
                    exception.GetType().FullName);
            else
                logger.LogError(exception, "Unhandled API exception");

            var problem = new ProblemDetails
            {
                Title = "An unexpected error occurred.",
                Status = StatusCodes.Status500InternalServerError,
                Detail = environment.IsDevelopment() ? exception.Message : null,
                Instance = context.Request.Path
            };
            problem.Extensions["traceId"] = context.TraceIdentifier;
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await context.Response.WriteAsJsonAsync(problem);
        }
    }

    private static async Task WriteProblemAsync(
        HttpContext context,
        ExceptionProblemDetailsMapping mapping)
    {
        var problem = new ProblemDetails
        {
            Title = mapping.Title,
            Status = mapping.Status,
            Instance = context.Request.Path
        };
        problem.Extensions["code"] = mapping.Code;
        problem.Extensions["traceId"] = context.TraceIdentifier;
        foreach (var extension in mapping.Extensions) problem.Extensions[extension.Key] = extension.Value;
        context.Response.StatusCode = mapping.Status;
        await context.Response.WriteAsJsonAsync(problem);
    }
}
