using System.Net;
using System.Text.Json;

namespace DodoSSH.Client.Api;

/// <summary>
/// A DodoSSH server returned an error.
/// </summary>
/// <remarks>
/// Carries the RFC 9457 <c>code</c> extension rather than only the status and prose. The codes are
/// constants in <c>DodoSSH.Contracts.ProblemCodes</c>, so the client can branch on
/// <see cref="Code"/> — <c>enrollment-required</c> means run enrollment, <c>vault-conflict</c> means
/// merge and retry — instead of matching on a message that is free to change.
/// </remarks>
public sealed class DodoSshApiException(HttpStatusCode statusCode, string? code, string message)
    : Exception(message)
{
    /// <summary>The HTTP status.</summary>
    public HttpStatusCode StatusCode { get; } = statusCode;

    /// <summary>The stable machine-readable code, when the server supplied one.</summary>
    public string? Code { get; } = code;

    /// <summary>
    /// Builds an exception from a response body.
    /// </summary>
    /// <remarks>
    /// Tolerates a body that is not ProblemDetails, or not JSON at all. A reverse proxy returning its
    /// own HTML 502 is a normal thing to meet, and losing the status code while trying to parse it
    /// would replace a diagnosable failure with a parse error.
    /// </remarks>
    internal static DodoSshApiException FromResponse(HttpStatusCode statusCode, string body)
    {
        string? code = null;
        var detail = body;

        try
        {
            using var document = JsonDocument.Parse(body);
            var root = document.RootElement;

            if (root.ValueKind == JsonValueKind.Object)
            {
                code = ReadString(root, "code");
                detail = ReadString(root, "detail") ?? ReadString(root, "title") ?? body;
            }
        }
        catch (JsonException)
        {
            // Not JSON. The status code still tells the caller what happened.
        }

        var summary = string.IsNullOrWhiteSpace(detail)
            ? $"The server returned {(int)statusCode}."
            : $"The server returned {(int)statusCode}: {detail}";

        return new DodoSshApiException(statusCode, code, summary);
    }

    private static string? ReadString(JsonElement root, string name) =>
        root.TryGetProperty(name, out var property) && property.ValueKind == JsonValueKind.String
            ? property.GetString()
            : null;
}
