using System.Collections.Immutable;
using System.Runtime.Serialization;
using System.Text;

namespace StellaOps.Vexer.Core;

public sealed record VexExportManifest
{
    public VexExportManifest(
        string exportId,
        VexQuerySignature querySignature,
        VexExportFormat format,
        DateTimeOffset createdAt,
        VexContentAddress artifact,
        int claimCount,
        IEnumerable<string> sourceProviders,
        bool fromCache = false,
        string? consensusRevision = null,
        VexAttestationMetadata? attestation = null,
        long sizeBytes = 0)
    {
        if (string.IsNullOrWhiteSpace(exportId))
        {
            throw new ArgumentException("Export id must be provided.", nameof(exportId));
        }

        if (claimCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(claimCount), "Claim count cannot be negative.");
        }

        if (sizeBytes < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(sizeBytes), "Export size cannot be negative.");
        }

        ExportId = exportId.Trim();
        QuerySignature = querySignature ?? throw new ArgumentNullException(nameof(querySignature));
        Format = format;
        CreatedAt = createdAt;
        Artifact = artifact ?? throw new ArgumentNullException(nameof(artifact));
        ClaimCount = claimCount;
        FromCache = fromCache;
        SourceProviders = NormalizeProviders(sourceProviders);
        ConsensusRevision = string.IsNullOrWhiteSpace(consensusRevision) ? null : consensusRevision.Trim();
        Attestation = attestation;
        SizeBytes = sizeBytes;
    }

    public string ExportId { get; }

    public VexQuerySignature QuerySignature { get; }

    public VexExportFormat Format { get; }

    public DateTimeOffset CreatedAt { get; }

    public VexContentAddress Artifact { get; }

    public int ClaimCount { get; }

    public bool FromCache { get; }

    public ImmutableArray<string> SourceProviders { get; }

    public string? ConsensusRevision { get; }

    public VexAttestationMetadata? Attestation { get; }

    public long SizeBytes { get; }

    private static ImmutableArray<string> NormalizeProviders(IEnumerable<string> providers)
    {
        if (providers is null)
        {
            throw new ArgumentNullException(nameof(providers));
        }

        var set = new SortedSet<string>(StringComparer.Ordinal);
        foreach (var provider in providers)
        {
            if (string.IsNullOrWhiteSpace(provider))
            {
                continue;
            }

            set.Add(provider.Trim());
        }

        return set.Count == 0
            ? ImmutableArray<string>.Empty
            : set.ToImmutableArray();
    }
}

public sealed record VexContentAddress
{
    public VexContentAddress(string algorithm, string digest)
    {
        if (string.IsNullOrWhiteSpace(algorithm))
        {
            throw new ArgumentException("Content algorithm must be provided.", nameof(algorithm));
        }

        if (string.IsNullOrWhiteSpace(digest))
        {
            throw new ArgumentException("Content digest must be provided.", nameof(digest));
        }

        Algorithm = algorithm.Trim();
        Digest = digest.Trim();
    }

    public string Algorithm { get; }

    public string Digest { get; }

    public string ToUri() => $"{Algorithm}:{Digest}";

    public override string ToString() => ToUri();
}

public sealed record VexAttestationMetadata
{
    public VexAttestationMetadata(
        string predicateType,
        VexRekorReference? rekor = null,
        string? envelopeDigest = null,
        DateTimeOffset? signedAt = null)
    {
        if (string.IsNullOrWhiteSpace(predicateType))
        {
            throw new ArgumentException("Predicate type must be provided.", nameof(predicateType));
        }

        PredicateType = predicateType.Trim();
        Rekor = rekor;
        EnvelopeDigest = string.IsNullOrWhiteSpace(envelopeDigest) ? null : envelopeDigest.Trim();
        SignedAt = signedAt;
    }

    public string PredicateType { get; }

    public VexRekorReference? Rekor { get; }

    public string? EnvelopeDigest { get; }

    public DateTimeOffset? SignedAt { get; }
}

public sealed record VexRekorReference
{
    public VexRekorReference(string apiVersion, string location, string? logIndex = null, Uri? inclusionProofUri = null)
    {
        if (string.IsNullOrWhiteSpace(apiVersion))
        {
            throw new ArgumentException("Rekor API version must be provided.", nameof(apiVersion));
        }

        if (string.IsNullOrWhiteSpace(location))
        {
            throw new ArgumentException("Rekor location must be provided.", nameof(location));
        }

        ApiVersion = apiVersion.Trim();
        Location = location.Trim();
        LogIndex = string.IsNullOrWhiteSpace(logIndex) ? null : logIndex.Trim();
        InclusionProofUri = inclusionProofUri;
    }

    public string ApiVersion { get; }

    public string Location { get; }

    public string? LogIndex { get; }

    public Uri? InclusionProofUri { get; }
}

public sealed partial record VexQuerySignature
{
    public VexQuerySignature(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException("Query signature must be provided.", nameof(value));
        }

        Value = value.Trim();
    }

    public string Value { get; }

    public static VexQuerySignature FromFilters(IEnumerable<KeyValuePair<string, string>> filters)
    {
        if (filters is null)
        {
            throw new ArgumentNullException(nameof(filters));
        }

        var builder = ImmutableArray.CreateBuilder<KeyValuePair<string, string>>();
        foreach (var pair in filters)
        {
            if (string.IsNullOrWhiteSpace(pair.Key))
            {
                continue;
            }

            var key = pair.Key.Trim();
            var value = pair.Value?.Trim() ?? string.Empty;
            builder.Add(new KeyValuePair<string, string>(key, value));
        }

        if (builder.Count == 0)
        {
            throw new ArgumentException("At least one filter is required to build a query signature.", nameof(filters));
        }

        var ordered = builder
            .OrderBy(static pair => pair.Key, StringComparer.Ordinal)
            .ThenBy(static pair => pair.Value, StringComparer.Ordinal)
            .ToImmutableArray();

        var sb = new StringBuilder();
        for (var i = 0; i < ordered.Length; i++)
        {
            if (i > 0)
            {
                sb.Append('&');
            }

            sb.Append(ordered[i].Key);
            sb.Append('=');
            sb.Append(ordered[i].Value);
        }

        return new VexQuerySignature(sb.ToString());
    }

    public override string ToString() => Value;
}

[DataContract]
public enum VexExportFormat
{
    [EnumMember(Value = "json")]
    Json,

    [EnumMember(Value = "jsonl")]
    JsonLines,

    [EnumMember(Value = "openvex")]
    OpenVex,

    [EnumMember(Value = "csaf")]
    Csaf,
}
