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

namespace StellaOps.Vexer.Storage.Mongo;

[BsonIgnoreExtraElements]
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>();

    [BsonRepresentation(BsonType.ObjectId)]
    public string? GridFsObjectId { get; set; }
        = null;

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

    public static VexRawDocumentRecord FromDomain(VexRawDocument document, bool includeContent = true)
        => new()
        {
            Id = document.Digest,
            ProviderId = document.ProviderId,
            Format = document.Format.ToString().ToLowerInvariant(),
            SourceUri = document.SourceUri.ToString(),
            RetrievedAt = document.RetrievedAt.UtcDateTime,
            Digest = document.Digest,
            Content = includeContent ? document.Content.ToArray() : Array.Empty<byte>(),
            Metadata = document.Metadata.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal),
        };

    public VexRawDocument ToDomain()
        => ToDomain(new ReadOnlyMemory<byte>(Content ?? Array.Empty<byte>()));

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

[BsonIgnoreExtraElements]
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.Format(CultureInfo.InvariantCulture, "{0}|{1}", signature.Value, format.ToString().ToLowerInvariant());
}

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

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

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

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

    public VexProviderDiscoveryDocument? Discovery { get; set; }
        = null;

    public VexProviderTrustDocument? Trust { get; set; }
        = null;

    public bool Enabled { get; set; }
        = true;

    public static VexProviderRecord FromDomain(VexProvider provider)
        => new()
        {
            Id = provider.Id,
            DisplayName = provider.DisplayName,
            Kind = provider.Kind.ToString().ToLowerInvariant(),
            BaseUris = provider.BaseUris.Select(uri => uri.ToString()).ToList(),
            Discovery = VexProviderDiscoveryDocument.FromDomain(provider.Discovery),
            Trust = VexProviderTrustDocument.FromDomain(provider.Trust),
            Enabled = provider.Enabled,
        };

    public VexProvider ToDomain()
    {
        var uris = BaseUris?.Select(uri => new Uri(uri)) ?? Enumerable.Empty<Uri>();
        return new VexProvider(
            Id,
            DisplayName,
            Enum.Parse<VexProviderKind>(Kind, ignoreCase: true),
            uris,
            Discovery?.ToDomain(),
            Trust?.ToDomain(),
            Enabled);
    }
}

[BsonIgnoreExtraElements]
internal sealed class VexProviderDiscoveryDocument
{
    public string? WellKnownMetadata { get; set; }
        = null;

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

    public static VexProviderDiscoveryDocument? FromDomain(VexProviderDiscovery? discovery)
        => discovery is null
            ? null
            : new VexProviderDiscoveryDocument
            {
                WellKnownMetadata = discovery.WellKnownMetadata?.ToString(),
                RolIeService = discovery.RolIeService?.ToString(),
            };

    public VexProviderDiscovery ToDomain()
        => new(
            WellKnownMetadata is null ? null : new Uri(WellKnownMetadata),
            RolIeService is null ? null : new Uri(RolIeService));
}

[BsonIgnoreExtraElements]
internal sealed class VexProviderTrustDocument
{
    public double Weight { get; set; }
        = 1.0;

    public VexCosignTrustDocument? Cosign { get; set; }
        = null;

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

    public static VexProviderTrustDocument? FromDomain(VexProviderTrust? trust)
        => trust is null
            ? null
            : new VexProviderTrustDocument
            {
                Weight = trust.Weight,
                Cosign = trust.Cosign is null ? null : VexCosignTrustDocument.FromDomain(trust.Cosign),
                PgpFingerprints = trust.PgpFingerprints.ToList(),
            };

    public VexProviderTrust ToDomain()
        => new(
            Weight,
            Cosign?.ToDomain(),
            PgpFingerprints);
}

[BsonIgnoreExtraElements]
internal sealed class VexCosignTrustDocument
{
    public string Issuer { get; set; } = default!;

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

    public static VexCosignTrustDocument FromDomain(VexCosignTrust trust)
        => new()
        {
            Issuer = trust.Issuer,
            IdentityPattern = trust.IdentityPattern,
        };

    public VexCosignTrust ToDomain()
        => new(Issuer, IdentityPattern);
}

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

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

    public VexProductDocument Product { get; set; } = default!;

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

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

    public List<VexConsensusSourceDocument> Sources { get; set; } = new();

    public List<VexConsensusConflictDocument> Conflicts { get; set; } = new();

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

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

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

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

    public static string CreateId(string vulnerabilityId, string productKey)
        => string.Format(CultureInfo.InvariantCulture, "{0}|{1}", vulnerabilityId.Trim(), productKey.Trim());

    public static VexConsensusRecord FromDomain(VexConsensus consensus)
        => new()
        {
            Id = CreateId(consensus.VulnerabilityId, consensus.Product.Key),
            VulnerabilityId = consensus.VulnerabilityId,
            Product = VexProductDocument.FromDomain(consensus.Product),
            Status = consensus.Status.ToString().ToLowerInvariant(),
            CalculatedAt = consensus.CalculatedAt.UtcDateTime,
            Sources = consensus.Sources.Select(VexConsensusSourceDocument.FromDomain).ToList(),
            Conflicts = consensus.Conflicts.Select(VexConsensusConflictDocument.FromDomain).ToList(),
            PolicyVersion = consensus.PolicyVersion,
            PolicyRevisionId = consensus.PolicyRevisionId,
            PolicyDigest = consensus.PolicyDigest,
            Summary = consensus.Summary,
        };

    public VexConsensus ToDomain()
        => new(
            VulnerabilityId,
            Product.ToDomain(),
            Enum.Parse<VexConsensusStatus>(Status, ignoreCase: true),
            new DateTimeOffset(CalculatedAt, TimeSpan.Zero),
            Sources.Select(static source => source.ToDomain()),
            Conflicts.Select(static conflict => conflict.ToDomain()),
            PolicyVersion,
            Summary,
            PolicyRevisionId,
            PolicyDigest);
}

[BsonIgnoreExtraElements]
internal sealed class VexProductDocument
{
    public string Key { get; set; } = default!;

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

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

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

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

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

    public static VexProductDocument FromDomain(VexProduct product)
        => new()
        {
            Key = product.Key,
            Name = product.Name,
            Version = product.Version,
            Purl = product.Purl,
            Cpe = product.Cpe,
            ComponentIdentifiers = product.ComponentIdentifiers.ToList(),
        };

    public VexProduct ToDomain()
        => new(
            Key,
            Name,
            Version,
            Purl,
            Cpe,
            ComponentIdentifiers);
}

[BsonIgnoreExtraElements]
internal sealed class VexConsensusSourceDocument
{
    public string ProviderId { get; set; } = default!;

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

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

    public double Weight { get; set; }
        = 0;

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

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

    public VexConfidenceDocument? Confidence { get; set; }
        = null;

    public static VexConsensusSourceDocument FromDomain(VexConsensusSource source)
        => new()
        {
            ProviderId = source.ProviderId,
            Status = source.Status.ToString().ToLowerInvariant(),
            DocumentDigest = source.DocumentDigest,
            Weight = source.Weight,
            Justification = source.Justification?.ToString().ToLowerInvariant(),
            Detail = source.Detail,
            Confidence = source.Confidence is null ? null : VexConfidenceDocument.FromDomain(source.Confidence),
        };

    public VexConsensusSource ToDomain()
        => new(
            ProviderId,
            Enum.Parse<VexClaimStatus>(Status, ignoreCase: true),
            DocumentDigest,
            Weight,
            string.IsNullOrWhiteSpace(Justification) ? null : Enum.Parse<VexJustification>(Justification, ignoreCase: true),
            Detail,
            Confidence?.ToDomain());
}

[BsonIgnoreExtraElements]
internal sealed class VexConsensusConflictDocument
{
    public string ProviderId { get; set; } = default!;

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

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

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

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

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

    public static VexConsensusConflictDocument FromDomain(VexConsensusConflict conflict)
        => new()
        {
            ProviderId = conflict.ProviderId,
            Status = conflict.Status.ToString().ToLowerInvariant(),
            DocumentDigest = conflict.DocumentDigest,
            Justification = conflict.Justification?.ToString().ToLowerInvariant(),
            Detail = conflict.Detail,
            Reason = conflict.Reason,
        };

    public VexConsensusConflict ToDomain()
        => new(
            ProviderId,
            Enum.Parse<VexClaimStatus>(Status, ignoreCase: true),
            DocumentDigest,
            string.IsNullOrWhiteSpace(Justification) ? null : Enum.Parse<VexJustification>(Justification, ignoreCase: true),
            Detail,
            Reason);
}

[BsonIgnoreExtraElements]
internal sealed class VexConfidenceDocument
{
    public string Level { get; set; } = default!;

    public double? Score { get; set; }
        = null;

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

    public static VexConfidenceDocument FromDomain(VexConfidence confidence)
        => new()
        {
            Level = confidence.Level,
            Score = confidence.Score,
            Method = confidence.Method,
        };

    public VexConfidence ToDomain()
        => new(Level, Score, Method);
}

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

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

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

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

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

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

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

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

    [BsonRepresentation(BsonType.ObjectId)]
    public string? GridFsObjectId { get; set; }
        = null;

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

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

    public static VexCacheEntryRecord FromDomain(VexCacheEntry entry)
        => new()
        {
            Id = CreateId(entry.QuerySignature, entry.Format),
            QuerySignature = entry.QuerySignature.Value,
            Format = entry.Format.ToString().ToLowerInvariant(),
            ArtifactAlgorithm = entry.Artifact.Algorithm,
            ArtifactDigest = entry.Artifact.Digest,
            CreatedAt = entry.CreatedAt.UtcDateTime,
            SizeBytes = entry.SizeBytes,
            ManifestId = entry.ManifestId,
            GridFsObjectId = entry.GridFsObjectId,
            ExpiresAt = entry.ExpiresAt?.UtcDateTime,
        };

    public VexCacheEntry ToDomain()
    {
        var signature = new VexQuerySignature(QuerySignature);
        var artifact = new VexContentAddress(ArtifactAlgorithm, ArtifactDigest);
        var createdAt = new DateTimeOffset(DateTime.SpecifyKind(CreatedAt, DateTimeKind.Utc));
        var expires = ExpiresAt.HasValue
            ? new DateTimeOffset(DateTime.SpecifyKind(ExpiresAt.Value, DateTimeKind.Utc))
            : (DateTimeOffset?)null;

        return new VexCacheEntry(
            signature,
            Enum.Parse<VexExportFormat>(Format, ignoreCase: true),
            artifact,
            createdAt,
            SizeBytes,
            ManifestId,
            GridFsObjectId,
            expires);
    }
}
