using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Vexer.Attestation.Dsse;

namespace StellaOps.Vexer.Attestation.Transparency;

internal sealed class RekorHttpClient : ITransparencyLogClient
{
    private readonly HttpClient _httpClient;
    private readonly RekorHttpClientOptions _options;
    private readonly ILogger<RekorHttpClient> _logger;

    public RekorHttpClient(HttpClient httpClient, IOptions<RekorHttpClientOptions> options, ILogger<RekorHttpClient> logger)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        ArgumentNullException.ThrowIfNull(options);
        _options = options.Value;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        if (!string.IsNullOrWhiteSpace(_options.BaseAddress))
        {
            _httpClient.BaseAddress = new Uri(_options.BaseAddress, UriKind.Absolute);
        }

        if (!string.IsNullOrWhiteSpace(_options.ApiKey))
        {
            _httpClient.DefaultRequestHeaders.Add("Authorization", _options.ApiKey);
        }
    }

    public async ValueTask<TransparencyLogEntry> SubmitAsync(DsseEnvelope envelope, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(envelope);
        var payload = JsonSerializer.Serialize(envelope);
        using var content = new StringContent(payload);
        content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

        HttpResponseMessage? response = null;
        for (var attempt = 0; attempt < _options.RetryCount; attempt++)
        {
            response = await _httpClient.PostAsync("/api/v2/log/entries", content, cancellationToken).ConfigureAwait(false);
            if (response.IsSuccessStatusCode)
            {
                break;
            }

            _logger.LogWarning("Rekor submission failed with status {Status}; attempt {Attempt}", response.StatusCode, attempt + 1);
            if (attempt + 1 < _options.RetryCount)
            {
                await Task.Delay(_options.RetryDelay, cancellationToken).ConfigureAwait(false);
            }
        }

        if (response is null || !response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Failed to submit attestation to Rekor ({response?.StatusCode}).");
        }

        var entryLocation = response.Headers.Location?.ToString() ?? string.Empty;
        var body = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: cancellationToken).ConfigureAwait(false);
        var entry = ParseEntryLocation(entryLocation, body);
        _logger.LogInformation("Rekor entry recorded at {Location}", entry.Location);
        return entry;
    }

    public async ValueTask<bool> VerifyAsync(string entryLocation, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(entryLocation))
        {
            return false;
        }

        var response = await _httpClient.GetAsync(entryLocation, cancellationToken).ConfigureAwait(false);
        return response.IsSuccessStatusCode;
    }

    private static TransparencyLogEntry ParseEntryLocation(string location, JsonElement body)
    {
        var id = body.TryGetProperty("uuid", out var uuid) ? uuid.GetString() ?? string.Empty : Guid.NewGuid().ToString();
        var logIndex = body.TryGetProperty("logIndex", out var logIndexElement) ? logIndexElement.GetString() : null;
        string? inclusionProof = null;
        if (body.TryGetProperty("verification", out var verification) && verification.TryGetProperty("inclusionProof", out var inclusion))
        {
            inclusionProof = inclusion.GetProperty("logIndex").GetRawText();
        }

        return new TransparencyLogEntry(id, location, logIndex, inclusionProof);
    }
}
