using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Vexer.Attestation.Models;
using StellaOps.Vexer.Attestation.Signing;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Attestation.Dsse;

public sealed class VexDsseBuilder
{
    private const string PayloadType = "application/vnd.in-toto+json";

    private readonly IVexSigner _signer;
    private readonly ILogger<VexDsseBuilder> _logger;
    private readonly JsonSerializerOptions _serializerOptions;

    public VexDsseBuilder(IVexSigner signer, ILogger<VexDsseBuilder> logger)
    {
        _signer = signer ?? throw new ArgumentNullException(nameof(signer));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _serializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.Never,
            WriteIndented = false,
        };
        _serializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
    }

    public async ValueTask<DsseEnvelope> CreateEnvelopeAsync(
        VexAttestationRequest request,
        IReadOnlyDictionary<string, string>? metadata,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(request);

        var predicate = VexAttestationPredicate.FromRequest(request, metadata);
        var subject = new VexInTotoSubject(
            request.ExportId,
            new Dictionary<string, string>(StringComparer.Ordinal)
            {
                { request.Artifact.Algorithm.ToLowerInvariant(), request.Artifact.Digest }
            });

        var statement = new VexInTotoStatement(
            VexInTotoStatement.InTotoType,
            "https://stella-ops.org/attestations/vex-export",
            new[] { subject },
            predicate);

        var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement, _serializerOptions);
        var signatureResult = await _signer.SignAsync(payloadBytes, cancellationToken).ConfigureAwait(false);

        var envelope = new DsseEnvelope(
            Convert.ToBase64String(payloadBytes),
            PayloadType,
            new[] { new DsseSignature(signatureResult.Signature, signatureResult.KeyId) });

        _logger.LogDebug("DSSE envelope created for export {ExportId}", request.ExportId);
        return envelope;
    }

    public static string ComputeEnvelopeDigest(DsseEnvelope envelope)
    {
        ArgumentNullException.ThrowIfNull(envelope);
        var envelopeJson = JsonSerializer.Serialize(envelope, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        });
        var bytes = Encoding.UTF8.GetBytes(envelopeJson);
        var hash = SHA256.HashData(bytes);
        return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
    }
}
