using System.Security.Cryptography;
using System.Text;

namespace YMhut.Box.Core.Feedback;

public sealed record EncryptedFeedbackPackage(
    string PackagePath,
    string PackageSha256,
    long PackageBytes);

public static class FeedbackPackageCrypto
{
    public const string MagicText = "YMHUTFB1";
    public const string EncryptionKeyMaterial = "ymhut-box-feedback-package-v1";

    private const int NonceSize = 12;
    private const int TagSize = 16;
    private static readonly byte[] Magic = Encoding.ASCII.GetBytes(MagicText);
    private static readonly byte[] Key = SHA256.HashData(Encoding.UTF8.GetBytes(EncryptionKeyMaterial));

    public static async Task<EncryptedFeedbackPackage> EncryptPackageAsync(
        string packagePath,
        string? encryptedPath = null,
        CancellationToken cancellationToken = default)
    {
        if (!File.Exists(packagePath))
        {
            throw new FileNotFoundException("反馈包不存在。", packagePath);
        }

        encryptedPath ??= Path.ChangeExtension(packagePath, ".ymfb");
        var plaintext = await File.ReadAllBytesAsync(packagePath, cancellationToken).ConfigureAwait(false);
        var nonce = RandomNumberGenerator.GetBytes(NonceSize);
        var tag = new byte[TagSize];
        var ciphertext = new byte[plaintext.Length];

        using (var aes = new AesGcm(Key, TagSize))
        {
            aes.Encrypt(nonce, plaintext, ciphertext, tag, Magic);
        }

        await using (var stream = File.Create(encryptedPath))
        {
            await stream.WriteAsync(Magic, cancellationToken).ConfigureAwait(false);
            await stream.WriteAsync(nonce, cancellationToken).ConfigureAwait(false);
            await stream.WriteAsync(tag, cancellationToken).ConfigureAwait(false);
            await stream.WriteAsync(ciphertext, cancellationToken).ConfigureAwait(false);
        }

        var hash = await HashFileAsync(encryptedPath, cancellationToken).ConfigureAwait(false);
        var info = new FileInfo(encryptedPath);
        return new EncryptedFeedbackPackage(info.FullName, hash, info.Length);
    }

    public static async Task DecryptPackageAsync(
        string encryptedPath,
        string outputPath,
        CancellationToken cancellationToken = default)
    {
        var payload = await File.ReadAllBytesAsync(encryptedPath, cancellationToken).ConfigureAwait(false);
        if (payload.Length < Magic.Length + NonceSize + TagSize ||
            !payload.AsSpan(0, Magic.Length).SequenceEqual(Magic))
        {
            throw new InvalidDataException("反馈包加密格式无效。");
        }

        var offset = Magic.Length;
        var nonce = payload.AsSpan(offset, NonceSize).ToArray();
        offset += NonceSize;
        var tag = payload.AsSpan(offset, TagSize).ToArray();
        offset += TagSize;
        var ciphertext = payload.AsSpan(offset).ToArray();
        var plaintext = new byte[ciphertext.Length];

        using (var aes = new AesGcm(Key, TagSize))
        {
            aes.Decrypt(nonce, ciphertext, tag, plaintext, Magic);
        }

        await File.WriteAllBytesAsync(outputPath, plaintext, cancellationToken).ConfigureAwait(false);
    }

    private static async Task<string> HashFileAsync(string path, CancellationToken cancellationToken)
    {
        await using var stream = File.OpenRead(path);
        var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
        return Convert.ToHexString(hash).ToLowerInvariant();
    }
}
