using System.Net.Http.Json;
using System.Text.Json;
using DodoSSH.Contracts;

namespace DodoSSH.Api.Tests;

/// <summary>
/// Sends and reads request bodies the way a real client does.
/// </summary>
/// <remarks>
/// <para>
/// Not a convenience. <c>PostAsJsonAsync</c>'s default options write an enum as a number, and the shared
/// contract writes it as a string. Tests that used the defaults therefore agreed with a server that had
/// also been left on the defaults, and the pair of them agreed on a wire form the specification never
/// described — so the entire sync surface was unreachable from the real client and every test passed.
/// </para>
/// <para>
/// Everything here goes through <see cref="DodoSshJsonContext"/> for that reason. If the server's JSON
/// configuration regresses, these tests are the ones that must fail.
/// </para>
/// </remarks>
internal static class ContractJson
{
    private static JsonSerializerOptions Options => DodoSshJsonContext.ResponseOptions;

    internal static Task<HttpResponseMessage> PostContractAsync<T>(
        this HttpClient client,
        string url,
        T value)
    {
        ArgumentNullException.ThrowIfNull(client);

        return client.PostAsJsonAsync(url, value, Options, TestContext.Current.CancellationToken);
    }

    /// <remarks>
    /// The same options as <see cref="PostContractAsync"/>, and here for the same reason rather than
    /// for symmetry: a PUT that serialised its enums differently from a POST would let a wire form the
    /// specification never described reach exactly the endpoints nothing else covers.
    /// </remarks>
    internal static Task<HttpResponseMessage> PutContractAsync<T>(
        this HttpClient client,
        string url,
        T value)
    {
        ArgumentNullException.ThrowIfNull(client);

        return client.PutAsJsonAsync(url, value, Options, TestContext.Current.CancellationToken);
    }

    internal static Task<T?> ReadContractAsync<T>(this HttpContent content)
    {
        ArgumentNullException.ThrowIfNull(content);

        return content.ReadFromJsonAsync<T>(Options, TestContext.Current.CancellationToken);
    }

    /// <summary>Reads an RFC 9457 problem body.</summary>
    /// <remarks>
    /// Deliberately <em>not</em> through the contract options. Problem details are written by the framework
    /// and are not part of <see cref="DodoSshJsonContext"/>'s source-generated set, so resolving them
    /// against it fails outright — a source-generated context does not fall back to reflection. Reading
    /// them with the ambient web options is correct rather than a shortcut: the shape is the RFC's, not
    /// ours, and only the <c>code</c> extension belongs to us.
    /// </remarks>
    internal static Task<JsonProblem?> ReadProblemAsync(this HttpContent content)
    {
        ArgumentNullException.ThrowIfNull(content);

        return content.ReadFromJsonAsync<JsonProblem>(TestContext.Current.CancellationToken);
    }
}
