using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using ZeroLevel.Services.Serialization;

namespace ZeroLevel.Services
{
    public class TokenEncryptor
    {
        private sealed class Cryptor
        {
            private const int Rfc2898KeygenIterations = 100;
            private const int AesKeySizeInBits = 128;
            private readonly byte[] _password;
            private readonly byte[] _salt;
            public Cryptor(string pwd, string salt)
            {
                _password = Encoding.ASCII.GetBytes(pwd);
                _salt = Encoding.ASCII.GetBytes(salt);
            }

            public byte[] Encrypt(byte[] data)
            {
                using (Aes aes = AesManaged.Create())
                {
                    aes.Padding = PaddingMode.PKCS7;
                    aes.KeySize = AesKeySizeInBits;
                    int KeyStrengthInBytes = aes.KeySize / 8;
                    System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
                        new System.Security.Cryptography.Rfc2898DeriveBytes(_password, _salt, Rfc2898KeygenIterations);
                    aes.Key = rfc2898.GetBytes(KeyStrengthInBytes);
                    aes.IV = rfc2898.GetBytes(KeyStrengthInBytes);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                        {
                            cs.Write(data, 0, data.Length);
                        }
                        return ms.ToArray();
                    }
                }
            }

            public byte[] Decrypt(byte[] data)
            {
                using (Aes aes = AesManaged.Create())
                {
                    aes.Padding = PaddingMode.PKCS7;
                    aes.KeySize = AesKeySizeInBits;
                    int KeyStrengthInBytes = aes.KeySize / 8;
                    System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
                        new System.Security.Cryptography.Rfc2898DeriveBytes(_password, _salt, Rfc2898KeygenIterations);
                    aes.Key = rfc2898.GetBytes(KeyStrengthInBytes);
                    aes.IV = rfc2898.GetBytes(KeyStrengthInBytes);

                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                        {
                            cs.Write(data, 0, data.Length);
                        }
                        return ms.ToArray();
                    }
                }
            }
        }

        private Cryptor _cryptor;
        public TokenEncryptor(string key, string salt)
        {
            _cryptor = new Cryptor(key, salt);
        }

        public T ReadFromToken<T>(string token)
            where T : IBinarySerializable
        {
            if (string.IsNullOrWhiteSpace(token) == false && string.CompareOrdinal(token, "null") != 0)
            {
                var data = Convert.FromBase64String(token);
                var decryptedBytes = _cryptor.Decrypt(data);
                var decryptedValue = MessageSerializer.Deserialize<T>(decryptedBytes);
                return decryptedValue;
            }
            return default!;
        }

        public string WriteToToken<T>(T value)
            where T : IBinarySerializable
        {
            var decryptedBytes = MessageSerializer.Serialize<T>(value);
            var encryptedBytes = _cryptor.Encrypt(decryptedBytes);
            var token = Convert.ToBase64String(encryptedBytes);
            return token;
        }
    }
}
