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

public class CryptoHelper
{
    private RSA rsa;
    private string _publicKeyRsa;

    public CryptoHelper(UserProperties userProperties)
    {
        string publicKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PublicKeyFile;
        string privateKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PrivateKeyFile;

        Console.WriteLine("Loading public key from: {0}", publicKeyPath);
        Console.WriteLine("Loading private key from: {0}", privateKeyPath);
        
        rsa = RSA.Create();
        rsa.ImportFromPem(File.ReadAllText(publicKeyPath));
        rsa.ImportFromPem(File.ReadAllText(privateKeyPath));

        _publicKeyRsa = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo());
    }

    public string GetPublicKeyRsa() => _publicKeyRsa;

    public byte[] DecryptEphemeralKey(string encryptedEphemeralKey)
    {
        return rsa.Decrypt(Convert.FromBase64String(encryptedEphemeralKey), RSAEncryptionPadding.OaepSHA512);
    }

    public async Task<string> DecryptTextAsync(byte[] decryptedEphemeralKey, string encryptedText)
    {
        var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedText);
        return Encoding.UTF8.GetString(decryptedBytes);
    }

    public async Task<string> DecryptPhotoAsync(byte[] decryptedEphemeralKey, string encryptedPhoto)
    {
        var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedPhoto);
        return Convert.ToBase64String(decryptedBytes);
    }

    public string EncryptText(byte[] decryptedEphemeralKey, string textValue) 
    {
        return EncryptContent(decryptedEphemeralKey, Encoding.UTF8.GetBytes(textValue));    
    }

    public string EncryptPhoto(byte[] decryptedEphemeralKey, byte[] photoBytes)
    {
        return EncryptContent(decryptedEphemeralKey, photoBytes);
    }

    private async Task<byte[]> DecryptContentAsync(byte[] decryptedEphemeralKey, string encryptedData)
    {
        byte[] encryptedDataByteArray = Convert.FromBase64String(encryptedData);
        using MemoryStream memoryStream = new(encryptedDataByteArray);
        using Aes aes = Aes.Create();
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;

        byte[] iv = new byte[aes.IV.Length];
        int numBytesToRead = aes.IV.Length;
        int numBytesRead = 0;
        while (numBytesToRead > 0)
        {
            int n = memoryStream.Read(iv, numBytesRead, numBytesToRead);
            if (n == 0)
            {
                break;
            }
            numBytesRead += n;
            numBytesToRead -= n;
        }

        byte[] key = decryptedEphemeralKey;

        await using CryptoStream cryptoStream = new(memoryStream,
                    aes.CreateDecryptor(key, iv),
                    CryptoStreamMode.Read);

        using MemoryStream ms = new();
        await cryptoStream.CopyToAsync(ms);
        var bytes = ms.ToArray();
        return bytes;
    }

    private string EncryptContent(byte[] decryptedEphemeralKey, byte[] content)
    {
        byte[] encrypted;
        byte[] iv;

        using (var aesAlg = Aes.Create())
        {
            aesAlg.Key = decryptedEphemeralKey;

            aesAlg.GenerateIV();
            iv = aesAlg.IV;

            aesAlg.Mode = CipherMode.CBC;
            aesAlg.Padding = PaddingMode.PKCS7;

            // Create an encryptor to perform the stream transform.
            var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new())
            {
                using (CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    
                        //Write all data to the stream.
                        csEncrypt.Write(content, 0, content.Length);
                    
                }
                encrypted = msEncrypt.ToArray();
            }
        }
        var combinedIvCt = new byte[iv.Length + encrypted.Length];
        Array.Copy(iv, 0, combinedIvCt, 0, iv.Length);
        Array.Copy(encrypted, 0, combinedIvCt, iv.Length, encrypted.Length);
        var encryptedData = Convert.ToBase64String(combinedIvCt);
        return encryptedData;
    }
}