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

namespace Knot.Shared.Kernel.Security;

public class AesEncryptionService : IEncryptionService
{
    private readonly byte[] _key;

    public AesEncryptionService(string masterKeyBase64)
    {
        // Try decoding as base64 or just using UTF8 string
        if (masterKeyBase64.Length == 32)
        {
            _key = Encoding.UTF8.GetBytes(masterKeyBase64);
        }
        else
        {
            try
            {
                _key = Convert.FromBase64String(masterKeyBase64);
            }
            catch
            {
                _key = Encoding.UTF8.GetBytes(masterKeyBase64.PadRight(32, '0')[..32]);
            }
        }

        if (_key.Length != 32)
        {
            throw new ArgumentException("Мастер-ключ должен быть ровно 32 байта для AES-256.");
        }
    }

    // Сообщения: AES-256-GCM
    public string EncryptMessage(string plainText)
    {
        if (string.IsNullOrEmpty(plainText))
        {
            return plainText;
        }

        var nonce = new byte[12];
        RandomNumberGenerator.Fill(nonce);

        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipherBytes = new byte[plainBytes.Length];
        var tag = new byte[16];

        using var aesGcm = new AesGcm(_key, 16);
        aesGcm.Encrypt(nonce, plainBytes, cipherBytes, tag);

        // формат: nonce:tag:cipherText
        return $"{Convert.ToBase64String(nonce)}:{Convert.ToBase64String(tag)}:{Convert.ToBase64String(cipherBytes)}";
    }

    public string DecryptMessage(string cipherText)
    {
        if (string.IsNullOrEmpty(cipherText))
        {
            return cipherText;
        }

        var parts = cipherText.Split(':');
        if (parts.Length != 3)
        {
            return cipherText;
        }

        try
        {
            var nonce = Convert.FromBase64String(parts[0]);
            var tag = Convert.FromBase64String(parts[1]);
            var cipherBytes = Convert.FromBase64String(parts[2]);
            var plainBytes = new byte[cipherBytes.Length];

            using var aesGcm = new AesGcm(_key, 16);
            aesGcm.Decrypt(nonce, cipherBytes, tag, plainBytes);

            return Encoding.UTF8.GetString(plainBytes);
        }
        catch
        {
            return cipherText; // Возвращаем оригинал при ошибке расшифровки (например, старые не зашифрованные сообщения)
        }
    }

    // Потоковые файлы: AES CBC через CryptoStream
    // (Примечание: Для GCM через CryptoStream нет поддержки "из коробки" в .NET при использовании Aes.Create().
    // Требуется CBC+HMAC/CTR или механизм AEAD блоками. Здесь предоставляется стандартный AES-CBC для соответствия требованиям CryptoStream).
    public Stream CreateEncryptionStream(Stream unencryptedOutputStream, out byte[] iv)
    {
        var aes = Aes.Create();
        aes.KeySize = 256;
        aes.Key = _key;
        aes.Mode = CipherMode.CBC;
        aes.GenerateIV();
        iv = aes.IV;

        var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        return new CryptoStream(unencryptedOutputStream, encryptor, CryptoStreamMode.Write);
    }

    public Stream CreateDecryptionStream(Stream encryptedInputStream, byte[] iv)
    {
        var aes = Aes.Create();
        aes.KeySize = 256;
        aes.Key = _key;
        aes.Mode = CipherMode.CBC;
        aes.IV = iv;

        var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
        return new CryptoStream(encryptedInputStream, decryptor, CryptoStreamMode.Read);
    }
}
