using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using ClaudeDo.Worker.Online.Interfaces;

namespace ClaudeDo.Worker.Online;

public sealed class OnlineInboxApiClient : IOnlineInboxApi
{
    internal const string MissingRoleMessage =
        "Account has no access (missing 'user' role in Zitadel). " +
        "Grant the 'user' role for this account in the ClaudeDo project, then sign in again.";

    private readonly HttpClient _http;
    private readonly IOnlineAuthProvider _auth;

    public OnlineInboxApiClient(HttpClient http, IOnlineAuthProvider auth)
    {
        _http = http;
        _auth = auth;
    }

    /// <summary>
    /// Validates that <paramref name="baseUrl"/> is HTTPS or a loopback address.
    /// Throws <see cref="InvalidOperationException"/> for non-HTTPS non-loopback URLs.
    /// </summary>
    public static void ValidateBaseUrl(string baseUrl)
    {
        if (string.IsNullOrWhiteSpace(baseUrl))
            throw new InvalidOperationException("online_inbox.api_base_url is not configured.");

        var uri = new Uri(baseUrl, UriKind.Absolute);
        if (uri.Scheme != "https" && !uri.IsLoopback)
            throw new InvalidOperationException(
                $"online_inbox.api_base_url must be HTTPS or loopback. Got: {baseUrl}");
    }

    public async Task PutListsAsync(IReadOnlyList<RemoteList> lists, CancellationToken ct = default)
    {
        using var resp = await SendAsync(HttpMethod.Put, "lists", lists, ct);
    }

    public async Task<IReadOnlyList<RemoteTask>> GetUnimportedTasksAsync(CancellationToken ct = default)
    {
        using var resp = await SendAsync(HttpMethod.Get, "tasks?imported=false", null, ct);
        var result = await resp.Content.ReadFromJsonAsync<List<RemoteTask>>(ct);
        return result ?? [];
    }

    public async Task MarkImportedAsync(string id, CancellationToken ct = default)
    {
        using var resp = await SendAsync(HttpMethod.Post, $"tasks/{Uri.EscapeDataString(id)}/imported", null, ct);
    }

    public async Task PutMirrorAsync(IReadOnlyList<MirrorTask> tasks, CancellationToken ct = default)
    {
        using var resp = await SendAsync(HttpMethod.Put, "tasks/mirror", tasks, ct);
    }

    /// <summary>
    /// Sends an authenticated request. On a 401, forces a fresh (role-bearing) token via the
    /// refresh-token grant and retries once; if a fresh token is still rejected, throws an
    /// <see cref="OnlineInboxException"/> with <see cref="MissingRoleMessage"/>.
    /// </summary>
    private async Task<HttpResponseMessage> SendAsync(
        HttpMethod method, string path, object? body, CancellationToken ct)
    {
        var resp = await SendOnceAsync(method, path, body, forceRefresh: false, ct);

        if (resp.StatusCode == HttpStatusCode.Unauthorized)
        {
            resp.Dispose();
            resp = await SendOnceAsync(method, path, body, forceRefresh: true, ct);
        }

        if (resp.StatusCode == HttpStatusCode.Unauthorized)
        {
            resp.Dispose();
            throw new OnlineInboxException(401, MissingRoleMessage);
        }

        if (!resp.IsSuccessStatusCode)
        {
            var status = (int)resp.StatusCode;
            var errBody = await resp.Content.ReadAsStringAsync(ct);
            resp.Dispose();
            throw new OnlineInboxException(status, $"Online Inbox API error {status}: {errBody}");
        }

        return resp;
    }

    private async Task<HttpResponseMessage> SendOnceAsync(
        HttpMethod method, string path, object? body, bool forceRefresh, CancellationToken ct)
    {
        var token = await _auth.GetAccessTokenAsync(forceRefresh, ct);
        using var req = new HttpRequestMessage(method, path);
        if (token is not null)
            req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        if (body is not null)
            req.Content = JsonContent.Create(body);
        return await _http.SendAsync(req, ct);
    }
}
