using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Vexer.Attestation.Dsse;
using StellaOps.Vexer.Attestation.Models;
using StellaOps.Vexer.Attestation.Signing;
using StellaOps.Vexer.Attestation.Transparency;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Attestation;

public sealed class VexAttestationClientOptions
{
    public IReadOnlyDictionary<string, string> DefaultMetadata { get; set; } = ImmutableDictionary<string, string>.Empty;
}

public sealed class VexAttestationClient : IVexAttestationClient
{
    private readonly VexDsseBuilder _builder;
    private readonly ILogger<VexAttestationClient> _logger;
    private readonly TimeProvider _timeProvider;
    private readonly IReadOnlyDictionary<string, string> _defaultMetadata;
    private readonly ITransparencyLogClient? _transparencyLogClient;

    public VexAttestationClient(
        VexDsseBuilder builder,
        IOptions<VexAttestationClientOptions> options,
        ILogger<VexAttestationClient> logger,
        TimeProvider? timeProvider = null,
        ITransparencyLogClient? transparencyLogClient = null)
    {
        _builder = builder ?? throw new ArgumentNullException(nameof(builder));
        ArgumentNullException.ThrowIfNull(options);
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _timeProvider = timeProvider ?? TimeProvider.System;
        _defaultMetadata = options.Value.DefaultMetadata;
        _transparencyLogClient = transparencyLogClient;
    }

    public async ValueTask<VexAttestationResponse> SignAsync(VexAttestationRequest request, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(request);
        var mergedMetadata = MergeMetadata(request.Metadata, _defaultMetadata);

        var envelope = await _builder.CreateEnvelopeAsync(request, mergedMetadata, cancellationToken).ConfigureAwait(false);
        var envelopeDigest = VexDsseBuilder.ComputeEnvelopeDigest(envelope);
        var signedAt = _timeProvider.GetUtcNow();

        var diagnosticsBuilder = ImmutableDictionary<string, string>.Empty
            .Add("envelope", JsonSerializer.Serialize(envelope))
            .Add("predicateType", "https://stella-ops.org/attestations/vex-export");

        VexRekorReference? rekorReference = null;
        if (_transparencyLogClient is not null)
        {
            try
            {
                var entry = await _transparencyLogClient.SubmitAsync(envelope, cancellationToken).ConfigureAwait(false);
                rekorReference = new VexRekorReference("0.2", entry.Location, entry.LogIndex, entry.InclusionProofUrl is not null ? new Uri(entry.InclusionProofUrl) : null);
                diagnosticsBuilder = diagnosticsBuilder.Add("rekorLocation", entry.Location);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to submit attestation to Rekor transparency log");
                throw;
            }
        }

        var metadata = new VexAttestationMetadata(
            predicateType: "https://stella-ops.org/attestations/vex-export",
            rekor: rekorReference,
            envelopeDigest: envelopeDigest,
            signedAt: signedAt);

        _logger.LogInformation("Generated DSSE envelope for export {ExportId} ({Digest})", request.ExportId, envelopeDigest);

        return new VexAttestationResponse(metadata, diagnosticsBuilder);
    }

    public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationRequest request, CancellationToken cancellationToken)
    {
        // Placeholder until verification flow is implemented in VEXER-ATTEST-01-003.
        return ValueTask.FromResult(new VexAttestationVerification(true, ImmutableDictionary<string, string>.Empty));
    }

    private static IReadOnlyDictionary<string, string> MergeMetadata(
        IReadOnlyDictionary<string, string> requestMetadata,
        IReadOnlyDictionary<string, string> defaults)
    {
        if (defaults.Count == 0)
        {
            return requestMetadata;
        }

        var merged = new Dictionary<string, string>(defaults, StringComparer.Ordinal);
        foreach (var kvp in requestMetadata)
        {
            merged[kvp.Key] = kvp.Value;
        }

        return merged.ToImmutableDictionary(StringComparer.Ordinal);
    }
}
