using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace ZB.MOM.WW.ScadaBridge.CLI;

public class ManagementHttpClient : IDisposable
{
    private readonly HttpClient _httpClient;

    /// <summary>
    /// Initializes a new instance of the <see cref="ManagementHttpClient"/> class.
    /// </summary>
    /// <param name="baseUrl">The base URL for the management API.</param>
    /// <param name="username">The username for HTTP Basic authentication.</param>
    /// <param name="password">The password for HTTP Basic authentication.</param>
    public ManagementHttpClient(string baseUrl, string username, string password)
        : this(new HttpClient(), baseUrl, username, password)
    {
    }

    /// <summary>
    /// Test-only constructor that accepts a pre-built <see cref="HttpClient"/> (typically
    /// over a stub <see cref="HttpMessageHandler"/>) so the request/response handling can
    /// be exercised without a live server.
    /// </summary>
    /// <param name="httpClient">The HTTP client to use for requests.</param>
    /// <param name="baseUrl">The base URL for the management API.</param>
    /// <param name="username">The username for HTTP Basic authentication.</param>
    /// <param name="password">The password for HTTP Basic authentication.</param>
    internal ManagementHttpClient(HttpClient httpClient, string baseUrl, string username, string password)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/");
        var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Basic", credentials);
    }

    /// <summary>
    /// Sends a management command to the management API.
    /// </summary>
    /// <param name="commandName">The command name to execute.</param>
    /// <param name="payload">The command payload.</param>
    /// <param name="timeout">The request timeout.</param>
    /// <returns>A management response containing status and data.</returns>
    public async Task<ManagementResponse> SendCommandAsync(string commandName, object payload, TimeSpan timeout)
    {
        using var cts = new CancellationTokenSource(timeout);

        var body = JsonSerializer.Serialize(new { command = commandName, payload },
            new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

        var content = new StringContent(body, Encoding.UTF8, "application/json");

        HttpResponseMessage httpResponse;
        try
        {
            httpResponse = await _httpClient.PostAsync("management", content, cts.Token);
        }
        catch (TaskCanceledException)
        {
            return new ManagementResponse(504, null, "Request timed out.", "TIMEOUT");
        }
        catch (HttpRequestException ex)
        {
            return new ManagementResponse(0, null, $"Connection failed: {ex.Message}", "CONNECTION_FAILED");
        }

        var responseBody = await httpResponse.Content.ReadAsStringAsync(cts.Token);

        if (httpResponse.IsSuccessStatusCode)
        {
            return new ManagementResponse((int)httpResponse.StatusCode, responseBody, null, null);
        }

        // Parse error response
        string? error = null;
        string? code = null;
        try
        {
            using var doc = JsonDocument.Parse(responseBody);
            error = doc.RootElement.TryGetProperty("error", out var e) ? e.GetString() : responseBody;
            code = doc.RootElement.TryGetProperty("code", out var c) ? c.GetString() : null;
        }
        catch
        {
            error = responseBody;
        }

        return new ManagementResponse((int)httpResponse.StatusCode, null, error, code);
    }

    /// <summary>
    /// Issues a plain HTTP <c>GET</c> against a REST endpoint (e.g. the audit
    /// <c>/api/audit/query</c> endpoint introduced by Audit Log #23 M8) and returns the
    /// response body. Unlike <see cref="SendCommandAsync"/>, this does not wrap the call
    /// in the <c>POST /management</c> command envelope — the audit endpoints are plain
    /// REST resources. Authentication (HTTP Basic) and the base address are shared.
    /// </summary>
    /// <param name="relativePath">Path relative to the base URL, with query string.</param>
    /// <param name="timeout">The request timeout.</param>
    /// <returns>A management response containing status and data.</returns>
    public async Task<ManagementResponse> SendGetAsync(string relativePath, TimeSpan timeout)
    {
        using var cts = new CancellationTokenSource(timeout);

        HttpResponseMessage httpResponse;
        try
        {
            httpResponse = await _httpClient.GetAsync(relativePath, cts.Token);
        }
        catch (TaskCanceledException)
        {
            return new ManagementResponse(504, null, "Request timed out.", "TIMEOUT");
        }
        catch (HttpRequestException ex)
        {
            return new ManagementResponse(0, null, $"Connection failed: {ex.Message}", "CONNECTION_FAILED");
        }

        var responseBody = await httpResponse.Content.ReadAsStringAsync(cts.Token);

        if (httpResponse.IsSuccessStatusCode)
        {
            return new ManagementResponse((int)httpResponse.StatusCode, responseBody, null, null);
        }

        string? error = null;
        string? code = null;
        try
        {
            using var doc = JsonDocument.Parse(responseBody);
            error = doc.RootElement.TryGetProperty("error", out var e) ? e.GetString() : responseBody;
            code = doc.RootElement.TryGetProperty("code", out var c) ? c.GetString() : null;
        }
        catch
        {
            error = responseBody;
        }

        return new ManagementResponse((int)httpResponse.StatusCode, null, error, code);
    }

    /// <summary>
    /// Issues a plain HTTP <c>GET</c> and returns the raw <see cref="HttpResponseMessage"/>
    /// so the caller can stream the response body without buffering it in memory — used
    /// by <c>audit export</c>, where the response can be many megabytes. The caller owns
    /// disposing the returned message. The <see cref="HttpCompletionOption.ResponseHeadersRead"/>
    /// option ensures the body is not pre-buffered.
    /// </summary>
    /// <param name="relativePath">Path relative to the base URL, with query string.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>The raw HTTP response message for streaming.</returns>
    public async Task<HttpResponseMessage> SendGetStreamAsync(string relativePath, CancellationToken cancellationToken)
        => await _httpClient.GetAsync(relativePath, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

    /// <summary>
    /// Disposes the underlying HTTP client.
    /// </summary>
    public void Dispose() => _httpClient.Dispose();
}

public record ManagementResponse(int StatusCode, string? JsonData, string? Error, string? ErrorCode);
