using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace StellaOps.Feedser.Exporter.TrivyDb;

public sealed class TrivyDbPackageBuilder
{
    private static readonly JsonSerializerOptions SerializerOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        WriteIndented = false,
    };

    public TrivyDbPackage BuildPackage(TrivyDbPackageRequest request)
    {
        if (request is null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (request.MetadataJson.IsEmpty)
        {
            throw new ArgumentException("Metadata JSON payload must be provided.", nameof(request));
        }

        if (string.IsNullOrWhiteSpace(request.DatabaseArchivePath))
        {
            throw new ArgumentException("Database archive path must be provided.", nameof(request));
        }

        if (!File.Exists(request.DatabaseArchivePath))
        {
            throw new FileNotFoundException("Database archive path not found.", request.DatabaseArchivePath);
        }

        if (string.IsNullOrWhiteSpace(request.DatabaseDigest))
        {
            throw new ArgumentException("Database archive digest must be provided.", nameof(request));
        }

        if (request.DatabaseLength < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(request.DatabaseLength));
        }

        var metadataBytes = request.MetadataJson;
        var generatedAt = request.GeneratedAt.ToUniversalTime();
        var configDocument = new TrivyConfigDocument(
            TrivyDbMediaTypes.TrivyConfig,
            generatedAt,
            request.DatabaseVersion,
            request.DatabaseDigest,
            request.DatabaseLength);

        var configBytes = JsonSerializer.SerializeToUtf8Bytes(configDocument, SerializerOptions);
        var configDigest = ComputeDigest(configBytes);

        var configDescriptor = new OciDescriptor(
            TrivyDbMediaTypes.TrivyConfig,
            configDigest,
            configBytes.LongLength,
            new Dictionary<string, string>
            {
                ["org.opencontainers.image.title"] = "config.json",
            });

        var layerDescriptor = new OciDescriptor(
            TrivyDbMediaTypes.TrivyLayer,
            request.DatabaseDigest,
            request.DatabaseLength,
            new Dictionary<string, string>
            {
                ["org.opencontainers.image.title"] = "db.tar.gz",
            });

        var manifest = new OciManifest(
            2,
            TrivyDbMediaTypes.OciManifest,
            configDescriptor,
            ImmutableArray.Create(layerDescriptor));

        var blobs = new SortedDictionary<string, TrivyDbBlob>(StringComparer.Ordinal)
        {
            [configDigest] = TrivyDbBlob.FromBytes(configBytes),
            [request.DatabaseDigest] = TrivyDbBlob.FromFile(request.DatabaseArchivePath, request.DatabaseLength),
        };

        return new TrivyDbPackage(manifest, configDocument, blobs, metadataBytes);
    }

    private static string ComputeDigest(ReadOnlySpan<byte> payload)
    {
        var hash = SHA256.HashData(payload);
        var hex = Convert.ToHexString(hash);
        Span<char> buffer = stackalloc char[7 + hex.Length]; // "sha256:" + hex
        buffer[0] = 's';
        buffer[1] = 'h';
        buffer[2] = 'a';
        buffer[3] = '2';
        buffer[4] = '5';
        buffer[5] = '6';
        buffer[6] = ':';
        for (var i = 0; i < hex.Length; i++)
        {
            buffer[7 + i] = char.ToLowerInvariant(hex[i]);
        }

        return new string(buffer);
    }
}
