using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using StellaOps.Excititor.Connectors.Abstractions;
using StellaOps.Excititor.Connectors.OCI.OpenVEX.Attest.Configuration;
using StellaOps.Excititor.Connectors.OCI.OpenVEX.Attest.Discovery;
using StellaOps.Excititor.Connectors.OCI.OpenVEX.Attest.Fetch;
using StellaOps.Excititor.Core;

namespace StellaOps.Excititor.Connectors.OCI.OpenVEX.Attest;

public sealed class OciOpenVexAttestationConnector : VexConnectorBase
{
    private static readonly VexConnectorDescriptor StaticDescriptor = new(
            id: "excititor:oci.openvex.attest",
            kind: VexProviderKind.Attestation,
            displayName: "OCI OpenVEX Attestations")
        {
            Tags = ImmutableArray.Create("oci", "openvex", "attestation", "cosign", "offline"),
        };

    private readonly OciAttestationDiscoveryService _discoveryService;
    private readonly OciAttestationFetcher _fetcher;
    private readonly IEnumerable<IVexConnectorOptionsValidator<OciOpenVexAttestationConnectorOptions>> _validators;

    private OciOpenVexAttestationConnectorOptions? _options;
    private OciAttestationDiscoveryResult? _discovery;

    public OciOpenVexAttestationConnector(
        OciAttestationDiscoveryService discoveryService,
        OciAttestationFetcher fetcher,
        ILogger<OciOpenVexAttestationConnector> logger,
        TimeProvider timeProvider,
        IEnumerable<IVexConnectorOptionsValidator<OciOpenVexAttestationConnectorOptions>>? validators = null)
        : base(StaticDescriptor, logger, timeProvider)
    {
        _discoveryService = discoveryService ?? throw new ArgumentNullException(nameof(discoveryService));
        _fetcher = fetcher ?? throw new ArgumentNullException(nameof(fetcher));
        _validators = validators ?? Array.Empty<IVexConnectorOptionsValidator<OciOpenVexAttestationConnectorOptions>>();
    }

    public override async ValueTask ValidateAsync(VexConnectorSettings settings, CancellationToken cancellationToken)
    {
        _options = VexConnectorOptionsBinder.Bind(
            Descriptor,
            settings,
            validators: _validators);

        _discovery = await _discoveryService.LoadAsync(_options, cancellationToken).ConfigureAwait(false);

        LogConnectorEvent(LogLevel.Information, "validate", "Resolved OCI attestation targets.", new Dictionary<string, object?>
        {
            ["targets"] = _discovery.Targets.Length,
            ["offlinePreferred"] = _discovery.PreferOffline,
            ["allowNetworkFallback"] = _discovery.AllowNetworkFallback,
            ["authMode"] = _discovery.RegistryAuthorization.Mode.ToString(),
            ["cosignMode"] = _discovery.CosignAuthority.Mode.ToString(),
        });
    }

    public override async IAsyncEnumerable<VexRawDocument> FetchAsync(VexConnectorContext context, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(context);

        if (_options is null)
        {
            throw new InvalidOperationException("Connector must be validated before fetch operations.");
        }

        if (_discovery is null)
        {
            _discovery = await _discoveryService.LoadAsync(_options, cancellationToken).ConfigureAwait(false);
        }

        var documentCount = 0;
        await foreach (var document in _fetcher.FetchAsync(_discovery, _options, cancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();

            var verificationDocument = CreateRawDocument(
                VexDocumentFormat.OciAttestation,
                document.SourceUri,
                document.Content,
                document.Metadata);

            var signatureMetadata = await context.SignatureVerifier.VerifyAsync(verificationDocument, cancellationToken).ConfigureAwait(false);
            if (signatureMetadata is not null)
            {
                LogConnectorEvent(LogLevel.Debug, "signature", "Signature metadata captured for attestation.", new Dictionary<string, object?>
                {
                    ["subject"] = signatureMetadata.Subject,
                    ["type"] = signatureMetadata.Type,
                });
            }

            var enrichedMetadata = BuildProvenanceMetadata(document, signatureMetadata);
            var rawDocument = CreateRawDocument(
                VexDocumentFormat.OciAttestation,
                document.SourceUri,
                document.Content,
                enrichedMetadata);

            await context.RawSink.StoreAsync(rawDocument, cancellationToken).ConfigureAwait(false);
            documentCount++;
            yield return rawDocument;
        }

        LogConnectorEvent(LogLevel.Information, "fetch", "OCI attestation fetch completed.", new Dictionary<string, object?>
        {
            ["documents"] = documentCount,
            ["since"] = context.Since?.ToString("O"),
        });
    }

    public override ValueTask<VexClaimBatch> NormalizeAsync(VexRawDocument document, CancellationToken cancellationToken)
        => throw new NotSupportedException("Attestation documents rely on dedicated normalizers, to be wired in EXCITITOR-CONN-OCI-01-002.");

    public OciAttestationDiscoveryResult? GetCachedDiscovery() => _discovery;

    private ImmutableDictionary<string, string> BuildProvenanceMetadata(OciAttestationDocument document, VexSignatureMetadata? signature)
    {
        var builder = document.Metadata.ToBuilder();

        if (!string.IsNullOrWhiteSpace(document.SourceKind))
        {
            builder["vex.provenance.sourceKind"] = document.SourceKind;
        }

        if (!string.IsNullOrWhiteSpace(document.SubjectDigest))
        {
            builder["vex.provenance.subjectDigest"] = document.SubjectDigest!;
        }

        if (!string.IsNullOrWhiteSpace(document.ArtifactDigest))
        {
            builder["vex.provenance.artifactDigest"] = document.ArtifactDigest!;
        }

        if (!string.IsNullOrWhiteSpace(document.ArtifactType))
        {
            builder["vex.provenance.artifactType"] = document.ArtifactType!;
        }

        if (_discovery is not null)
        {
            builder["vex.provenance.registryAuthMode"] = _discovery.RegistryAuthorization.Mode.ToString();
            var registryAuthority = _discovery.RegistryAuthorization.RegistryAuthority;
            if (string.IsNullOrWhiteSpace(registryAuthority))
            {
                if (builder.TryGetValue("oci.image.registry", out var metadataRegistry) && !string.IsNullOrWhiteSpace(metadataRegistry))
                {
                    registryAuthority = metadataRegistry;
                }
            }

            if (!string.IsNullOrWhiteSpace(registryAuthority))
            {
                builder["vex.provenance.registryAuthority"] = registryAuthority!;
            }

            builder["vex.provenance.cosign.mode"] = _discovery.CosignAuthority.Mode.ToString();

            if (_discovery.CosignAuthority.Keyless is not null)
            {
                var keyless = _discovery.CosignAuthority.Keyless;
                builder["vex.provenance.cosign.issuer"] = keyless!.Issuer;
                builder["vex.provenance.cosign.subject"] = keyless.Subject;
                if (keyless.FulcioUrl is not null)
                {
                    builder["vex.provenance.cosign.fulcioUrl"] = keyless.FulcioUrl!.ToString();
                }

                if (keyless.RekorUrl is not null)
                {
                    builder["vex.provenance.cosign.rekorUrl"] = keyless.RekorUrl!.ToString();
                }
            }
            else if (_discovery.CosignAuthority.KeyPair is not null)
            {
                var keyPair = _discovery.CosignAuthority.KeyPair;
                builder["vex.provenance.cosign.keyPair"] = "true";
                if (keyPair!.RekorUrl is not null)
                {
                    builder["vex.provenance.cosign.rekorUrl"] = keyPair.RekorUrl!.ToString();
                }
            }
        }

        if (signature is not null)
        {
            builder["vex.signature.type"] = signature.Type;
            if (!string.IsNullOrWhiteSpace(signature.Subject))
            {
                builder["vex.signature.subject"] = signature.Subject!;
            }

            if (!string.IsNullOrWhiteSpace(signature.Issuer))
            {
                builder["vex.signature.issuer"] = signature.Issuer!;
            }

            if (!string.IsNullOrWhiteSpace(signature.KeyId))
            {
                builder["vex.signature.keyId"] = signature.KeyId!;
            }

            if (signature.VerifiedAt is not null)
            {
                builder["vex.signature.verifiedAt"] = signature.VerifiedAt.Value.ToString("O");
            }

            if (!string.IsNullOrWhiteSpace(signature.TransparencyLogReference))
            {
                builder["vex.signature.transparencyLogReference"] = signature.TransparencyLogReference!;
            }
        }

        return builder.ToImmutable();
    }
}
