using System.Collections.Immutable;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using StellaOps.Excititor.Core;

namespace StellaOps.Excititor.Connectors.Abstractions;

/// <summary>
/// Convenience base class for implementing <see cref="IVexConnector" />.
/// </summary>
public abstract class VexConnectorBase : IVexConnector
{
    protected VexConnectorBase(VexConnectorDescriptor descriptor, ILogger logger, TimeProvider? timeProvider = null)
    {
        Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        TimeProvider = timeProvider ?? TimeProvider.System;
    }

    /// <inheritdoc />
    public string Id => Descriptor.Id;

    /// <inheritdoc />
    public VexProviderKind Kind => Descriptor.Kind;

    protected VexConnectorDescriptor Descriptor { get; }

    protected ILogger Logger { get; }

    protected TimeProvider TimeProvider { get; }

    protected DateTimeOffset UtcNow() => TimeProvider.GetUtcNow();

    protected VexRawDocument CreateRawDocument(
        VexDocumentFormat format,
        Uri sourceUri,
        ReadOnlyMemory<byte> content,
        ImmutableDictionary<string, string>? metadata = null)
    {
        if (sourceUri is null)
        {
            throw new ArgumentNullException(nameof(sourceUri));
        }

        var digest = ComputeSha256(content.Span);
        var captured = TimeProvider.GetUtcNow();
        return new VexRawDocument(
            Descriptor.Id,
            format,
            sourceUri,
            captured,
            digest,
            content,
            metadata ?? ImmutableDictionary<string, string>.Empty);
    }

    protected IDisposable BeginConnectorScope(string operation, IReadOnlyDictionary<string, object?>? metadata = null)
        => VexConnectorLogScope.Begin(Logger, Descriptor, operation, metadata);

    protected void LogConnectorEvent(LogLevel level, string eventName, string message, IReadOnlyDictionary<string, object?>? metadata = null, Exception? exception = null)
    {
        using var scope = BeginConnectorScope(eventName, metadata);
        if (exception is null)
        {
            Logger.Log(level, "{Message}", message);
        }
        else
        {
            Logger.Log(level, exception, "{Message}", message);
        }
    }

    protected ImmutableDictionary<string, string> BuildMetadata(Action<VexConnectorMetadataBuilder> configure)
    {
        ArgumentNullException.ThrowIfNull(configure);
        var builder = new VexConnectorMetadataBuilder();
        configure(builder);
        return builder.Build();
    }

    private static string ComputeSha256(ReadOnlySpan<byte> content)
    {
        Span<byte> buffer = stackalloc byte[32];
        if (SHA256.TryHashData(content, buffer, out _))
        {
            return "sha256:" + Convert.ToHexString(buffer).ToLowerInvariant();
        }

        using var sha = SHA256.Create();
        var hash = sha.ComputeHash(content.ToArray());
        return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
    }

    public abstract ValueTask ValidateAsync(VexConnectorSettings settings, CancellationToken cancellationToken);

    public abstract IAsyncEnumerable<VexRawDocument> FetchAsync(VexConnectorContext context, CancellationToken cancellationToken);

    public abstract ValueTask<VexClaimBatch> NormalizeAsync(VexRawDocument document, CancellationToken cancellationToken);
}
