using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace StellaOps.Feedser.Exporter.TrivyDb;

public sealed class TrivyDbBlob
{
    private readonly Func<CancellationToken, ValueTask<Stream>> _openReadAsync;

    private TrivyDbBlob(Func<CancellationToken, ValueTask<Stream>> openReadAsync, long length)
    {
        _openReadAsync = openReadAsync ?? throw new ArgumentNullException(nameof(openReadAsync));
        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length));
        }

        Length = length;
    }

    public long Length { get; }

    public ValueTask<Stream> OpenReadAsync(CancellationToken cancellationToken)
        => _openReadAsync(cancellationToken);

    public static TrivyDbBlob FromBytes(ReadOnlyMemory<byte> payload)
    {
        if (payload.IsEmpty)
        {
            return new TrivyDbBlob(static _ => ValueTask.FromResult<Stream>(Stream.Null), 0);
        }

        if (MemoryMarshal.TryGetArray(payload, out ArraySegment<byte> segment) && segment.Array is not null && segment.Offset == 0)
        {
            return FromArray(segment.Array);
        }

        return FromArray(payload.ToArray());
    }

    public static TrivyDbBlob FromFile(string path, long length)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("File path must be provided.", nameof(path));
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length));
        }

        return new TrivyDbBlob(
            cancellationToken => ValueTask.FromResult<Stream>(new FileStream(
                path,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read,
                bufferSize: 81920,
                options: FileOptions.Asynchronous | FileOptions.SequentialScan)),
            length);
    }

    public static TrivyDbBlob FromArray(byte[] buffer)
    {
        if (buffer is null)
        {
            throw new ArgumentNullException(nameof(buffer));
        }

        return new TrivyDbBlob(
            _ => ValueTask.FromResult<Stream>(new MemoryStream(buffer, writable: false)),
            buffer.LongLength);
    }
}
