using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using MongoDB.Bson.Serialization.Attributes;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Storage.Mongo;

internal sealed class VexRawDocumentRecord
{
    [BsonId]
    public string Id { get; set; } = default!;

    public string ProviderId { get; set; } = default!;

    public string Format { get; set; } = default!;

    public string SourceUri { get; set; } = default!;

    public DateTime RetrievedAt { get; set; }
        = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);

    public string Digest { get; set; } = default!;

    public byte[] Content { get; set; } = Array.Empty<byte>();

    public Dictionary<string, string> Metadata { get; set; } = new(StringComparer.Ordinal);

    public static VexRawDocumentRecord FromDomain(VexRawDocument document)
        => new()
        {
            Id = document.Digest,
            ProviderId = document.ProviderId,
            Format = document.Format.ToString().ToLowerInvariant(),
            SourceUri = document.SourceUri.ToString(),
            RetrievedAt = document.RetrievedAt.UtcDateTime,
            Digest = document.Digest,
            Content = document.Content.ToArray(),
            Metadata = document.Metadata.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal),
        };

    public VexRawDocument ToDomain()
        => new(
            ProviderId,
            Enum.Parse<VexDocumentFormat>(Format, ignoreCase: true),
            new Uri(SourceUri),
            RetrievedAt,
            Digest,
            new ReadOnlyMemory<byte>(Content ?? Array.Empty<byte>()),
            (Metadata ?? new Dictionary<string, string>(StringComparer.Ordinal))
                .ToImmutableDictionary(StringComparer.Ordinal));
}

internal sealed class VexExportManifestRecord
{
    [BsonId]
    public string Id { get; set; } = default!;

    public string QuerySignature { get; set; } = default!;

    public string Format { get; set; } = default!;

    public DateTime CreatedAt { get; set; }
        = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);

    public string ArtifactAlgorithm { get; set; } = default!;

    public string ArtifactDigest { get; set; } = default!;

    public int ClaimCount { get; set; }
        = 0;

    public bool FromCache { get; set; }
        = false;

    public List<string> SourceProviders { get; set; } = new();

    public string? ConsensusRevision { get; set; }
        = null;

    public string? PredicateType { get; set; }
        = null;

    public string? RekorApiVersion { get; set; }
        = null;

    public string? RekorLocation { get; set; }
        = null;

    public string? RekorLogIndex { get; set; }
        = null;

    public string? RekorInclusionProofUri { get; set; }
        = null;

    public string? EnvelopeDigest { get; set; }
        = null;

    public DateTime? SignedAt { get; set; }
        = null;

    public long SizeBytes { get; set; }
        = 0;

    public static VexExportManifestRecord FromDomain(VexExportManifest manifest)
        => new()
        {
            Id = CreateId(manifest.QuerySignature, manifest.Format),
            QuerySignature = manifest.QuerySignature.Value,
            Format = manifest.Format.ToString().ToLowerInvariant(),
            CreatedAt = manifest.CreatedAt.UtcDateTime,
            ArtifactAlgorithm = manifest.Artifact.Algorithm,
            ArtifactDigest = manifest.Artifact.Digest,
            ClaimCount = manifest.ClaimCount,
            FromCache = manifest.FromCache,
            SourceProviders = manifest.SourceProviders.ToList(),
            ConsensusRevision = manifest.ConsensusRevision,
            PredicateType = manifest.Attestation?.PredicateType,
            RekorApiVersion = manifest.Attestation?.Rekor?.ApiVersion,
            RekorLocation = manifest.Attestation?.Rekor?.Location,
            RekorLogIndex = manifest.Attestation?.Rekor?.LogIndex,
            RekorInclusionProofUri = manifest.Attestation?.Rekor?.InclusionProofUri?.ToString(),
            EnvelopeDigest = manifest.Attestation?.EnvelopeDigest,
            SignedAt = manifest.Attestation?.SignedAt?.UtcDateTime,
            SizeBytes = manifest.SizeBytes,
        };

    public VexExportManifest ToDomain()
    {
        var signedAt = SignedAt.HasValue
            ? new DateTimeOffset(DateTime.SpecifyKind(SignedAt.Value, DateTimeKind.Utc))
            : (DateTimeOffset?)null;

        var attestation = PredicateType is null
            ? null
            : new VexAttestationMetadata(
                PredicateType,
                RekorApiVersion is null || RekorLocation is null
                    ? null
                    : new VexRekorReference(
                        RekorApiVersion,
                        RekorLocation,
                        RekorLogIndex,
                        RekorInclusionProofUri is null ? null : new Uri(RekorInclusionProofUri)),
                EnvelopeDigest,
                signedAt);

        return new VexExportManifest(
            Id,
            new VexQuerySignature(QuerySignature),
            Enum.Parse<VexExportFormat>(Format, ignoreCase: true),
            CreatedAt,
            new VexContentAddress(ArtifactAlgorithm, ArtifactDigest),
            ClaimCount,
            SourceProviders,
            FromCache,
            ConsensusRevision,
            attestation,
            SizeBytes);
    }

    public static string CreateId(VexQuerySignature signature, VexExportFormat format)
        => string.Create(CultureInfo.InvariantCulture, $"{signature.Value}|{format.ToString().ToLowerInvariant()}");
}
