using System.Text.Json;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

namespace SatelliteProvider.Api;

public sealed class GlobalExceptionHandler : IExceptionHandler
{
    private const string JsonFieldErrorMessage = "The field value is invalid.";
    private const string BadRequestDetailMessage = "The request could not be processed.";

    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        // Framework-level request-binding/parsing failures carry their own HTTP status
        // (typically 400/415). Honor that status so we don't promote a client error to 5xx.
        if (exception is BadHttpRequestException badRequest)
        {
            await WriteClientErrorAsync(httpContext, badRequest, cancellationToken);
            return true;
        }

        var correlationId = httpContext.TraceIdentifier;

        _logger.LogError(
            exception,
            "Unhandled exception while processing {Method} {Path} (correlationId={CorrelationId})",
            httpContext.Request.Method,
            httpContext.Request.Path,
            correlationId);

        httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;

        var problem = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "Internal Server Error",
            Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry.",
            Type = "https://datatracker.ietf.org/doc/html/rfc9110#name-500-internal-server-error",
        };
        problem.Extensions["correlationId"] = correlationId;

        await httpContext.Response.WriteAsJsonAsync(
            problem,
            options: null,
            contentType: "application/problem+json",
            cancellationToken: cancellationToken);
        return true;
    }

    private static async Task WriteClientErrorAsync(
        HttpContext httpContext,
        BadHttpRequestException badRequest,
        CancellationToken cancellationToken)
    {
        httpContext.Response.StatusCode = badRequest.StatusCode;

        // AZ-795: deserialization failures (unknown field via UnmappedMemberHandling.Disallow,
        // type mismatch, malformed JSON) surface here as BadHttpRequestException with a
        // System.Text.Json `JsonException` somewhere in the inner-exception chain. Convert
        // them to RFC 7807 ValidationProblemDetails so wire-format errors share the same
        // shape as FluentValidation business-rule errors — see
        // `_docs/02_document/contracts/api/error-shape.md`.
        var deserializationErrors = TryExtractDeserializationErrors(badRequest);
        if (deserializationErrors is not null && badRequest.StatusCode == StatusCodes.Status400BadRequest)
        {
            var validation = new ValidationProblemDetails(deserializationErrors)
            {
                Status = badRequest.StatusCode,
                Title = "One or more validation errors occurred.",
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
            };

            await httpContext.Response.WriteAsJsonAsync(
                validation,
                options: null,
                contentType: "application/problem+json",
                cancellationToken: cancellationToken);
            return;
        }

        var problem = new ProblemDetails
        {
            Status = badRequest.StatusCode,
            Title = "Bad Request",
            Detail = BadRequestDetailMessage,
        };

        await httpContext.Response.WriteAsJsonAsync(
            problem,
            options: null,
            contentType: "application/problem+json",
            cancellationToken: cancellationToken);
    }

    private static IDictionary<string, string[]>? TryExtractDeserializationErrors(BadHttpRequestException ex)
    {
        var current = ex.InnerException;
        while (current is not null)
        {
            if (current is JsonException jsonEx)
            {
                var path = NormalizeJsonPath(jsonEx.Path);

                return new Dictionary<string, string[]>
                {
                    [path] = new[] { JsonFieldErrorMessage }
                };
            }

            current = current.InnerException;
        }

        return null;
    }

    private static string NormalizeJsonPath(string? path)
    {
        if (string.IsNullOrEmpty(path)) return "$";
        return path.StartsWith("$.", StringComparison.Ordinal)
            ? path.Substring(2)
            : path;
    }
}
