using Edge.Core.Parser.BinaryParser.Util;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace ShengJuDesFireEv1CpuCardKeyLoader
{
    public interface IKeyLoader
    {
        DecryptedKeySet Get(EncryptedBase64KeySet input);
    }

    public class DecryptedKeySet
    {
        public byte[] RootKey { get; set; }
        public byte[] 文件1读密钥 { get; set; }
        public byte[] 文件1读写密钥 { get; set; }

        public byte[] 文件2读密钥 { get; set; }
        public byte[] 文件2读写密钥 { get; set; }

        public byte[] 文件3读密钥 { get; set; }
        public byte[] 文件3读写密钥 { get; set; }

        public byte[] 文件4读密钥 { get; set; }
        public byte[] 文件4读写密钥 { get; set; }
    }

    public class InputBase64PlainKeySet
    {
        public string RootKey { get; set; }
        public string 文件1读密钥 { get; set; }
        public string 文件1读写密钥 { get; set; }

        public string 文件2读密钥 { get; set; }
        public string 文件2读写密钥 { get; set; }

        public string 文件3读密钥 { get; set; }
        public string 文件3读写密钥 { get; set; }

        public string 文件4读密钥 { get; set; }
        public string 文件4读写密钥 { get; set; }

        public string Description { get; set; }
    }

    public class EncryptedBase64KeySet
    {
        public string RootKey { get; set; }
        public string 文件1读密钥 { get; set; }
        public string 文件1读写密钥 { get; set; }

        public string 文件2读密钥 { get; set; }
        public string 文件2读写密钥 { get; set; }

        public string 文件3读密钥 { get; set; }
        public string 文件3读写密钥 { get; set; }

        public string 文件4读密钥 { get; set; }
        public string 文件4读写密钥 { get; set; }

        public string Description { get; set; }
    }

    public class LocalNetworkMacBasedEncryptionKeyLoader : IKeyLoader
    {
        private ILogger logger = NullLogger.Instance;
        public LocalNetworkMacBasedEncryptionKeyLoader(ILogger logger)
        {
            this.logger = logger;
        }

        /// <summary>
        /// </summary>
        /// <returns></returns>
        private byte[] GetEncryptionKeySeed()
        {
            var nics = NetworkInterface.GetAllNetworkInterfaces()
                     .Where(i => i.SupportsMulticast && i.Supports(NetworkInterfaceComponent.IPv4) && i.NetworkInterfaceType == NetworkInterfaceType.Ethernet);
            List<byte[]> macAddresses = new List<byte[]>();
            string logStr = "";
            for (int i = 0; i < nics.Count(); i++)
            {
                var adapter = nics.ElementAt(i);
                IPInterfaceProperties properties = adapter.GetIPProperties(); //  .GetIPInterfaceProperties();
                logStr += $"===========Ethernet {i}: {adapter.Description}==========={Environment.NewLine}";
                logStr += $"  Interface type .......................... : {adapter.NetworkInterfaceType}" + Environment.NewLine;
                logStr += $"  OperationalStatus ....................... : {adapter.OperationalStatus}" + Environment.NewLine;
                logStr += $"  IpAddresses ....................... : " +
                    $"{properties.UnicastAddresses.Select(ad => ad.Address).Where(ad => ad.AddressFamily == AddressFamily.InterNetwork).Select(i => i.ToString()).Aggregate("", (acc, n) => acc + ", " + n)}" + Environment.NewLine;
                logStr += $"  Physical address ........................ : 0x{adapter.GetPhysicalAddress().GetAddressBytes().ToHexLogString()}" + Environment.NewLine;
                if (adapter.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase)
                    || adapter.Description.Contains("docker", StringComparison.OrdinalIgnoreCase))
                {
                    logStr += "        --->>Virutal Device<<---" + Environment.NewLine;
                    continue;
                }

                macAddresses.Add(adapter.GetPhysicalAddress().GetAddressBytes());
            }

            this.logger.LogInformation($"KeyLoader is preparing for GetEncryptionKey: {Environment.NewLine}{logStr}");
            if (!macAddresses.Any())
                throw new ArgumentException("Failed to generate Encryption Key from local Ethernet NetworkInterface mac addresses");
            List<byte> raw = new List<byte>();
            for (int i = 0; i < macAddresses[0].Length; i++)
            {
                byte accu = 0;
                for (int j = 0; j < macAddresses.Count; j++)
                {
                    accu ^= macAddresses[j][i];
                }

                raw.Add(accu);
            }

            //var base64Encoded = Convert.ToBase64String(raw.ToArray());
            this.logger.LogInformation("raw EncryptionKey seed hex: 0x" + raw.ToHexLogString());// + ", base64 encoded: " + base64Encoded);
            return raw.ToArray();
        }

        /// <summary>
        /// Get the 16 bytes encryption key
        /// </summary>
        /// <returns></returns>
        protected virtual byte[] GetEncryptionKey()
        {
            var encryptionKeySeed = this.GetEncryptionKeySeed();
            byte[] symmetricKey = encryptionKeySeed.Concat(Enumerable.Range(0, 16 - encryptionKeySeed.Length).Select(i => (byte)i)).ToArray();
            return symmetricKey;
        }

        public DecryptedKeySet Get(EncryptedBase64KeySet input)
        {
            var output = new DecryptedKeySet();
            if (!string.IsNullOrEmpty(input.RootKey))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.RootKey.Length]);
                if (Convert.TryFromBase64String(input.RootKey, buffer, out int bytesWritten))
                    output.RootKey = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped RootKey must be Base64 encodeded format before get decrypting");
            }


            if (!string.IsNullOrEmpty(input.文件1读写密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件1读写密钥.Length]);
                if (Convert.TryFromBase64String(input.文件1读写密钥, buffer, out int bytesWritten))
                    output.文件1读写密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件1读写密钥 must be Base64 encodeded format before get decrypting");
            }

            if (!string.IsNullOrEmpty(input.文件1读密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件1读密钥.Length]);
                if (Convert.TryFromBase64String(input.文件1读写密钥, buffer, out int bytesWritten))
                    output.文件1读密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件1读密钥 must be Base64 encodeded format before get decrypting");
            }


            if (!string.IsNullOrEmpty(input.文件2读写密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件2读写密钥.Length]);
                if (Convert.TryFromBase64String(input.文件2读写密钥, buffer, out int bytesWritten))
                    output.文件2读写密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件2读写密钥 must be Base64 encodeded format before get decrypting");
            }

            if (!string.IsNullOrEmpty(input.文件2读密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件2读密钥.Length]);
                if (Convert.TryFromBase64String(input.文件2读密钥, buffer, out int bytesWritten))
                    output.文件2读密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件2读密钥 must be Base64 encodeded format before get decrypting");
            }


            if (!string.IsNullOrEmpty(input.文件3读写密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件3读写密钥.Length]);
                if (Convert.TryFromBase64String(input.文件3读写密钥, buffer, out int bytesWritten))
                    output.文件3读写密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件3读写密钥 must be Base64 encodeded format before get decrypting");
            }

            if (!string.IsNullOrEmpty(input.文件3读密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件3读密钥.Length]);
                if (Convert.TryFromBase64String(input.文件3读密钥, buffer, out int bytesWritten))
                    output.文件3读密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件3读密钥 must be Base64 encodeded format before get decrypting");
            }


            if (!string.IsNullOrEmpty(input.文件4读写密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件4读写密钥.Length]);
                if (Convert.TryFromBase64String(input.文件4读写密钥, buffer, out int bytesWritten))
                    output.文件4读写密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件4读写密钥 must be Base64 encodeded format before get decrypting");
            }

            if (!string.IsNullOrEmpty(input.文件4读密钥))
            {
                Span<byte> buffer = new Span<byte>(new byte[input.文件4读密钥.Length]);
                if (Convert.TryFromBase64String(input.文件4读密钥, buffer, out int bytesWritten))
                    output.文件4读密钥 = this.Decrypt(buffer.ToArray().Take(bytesWritten).ToArray());
                else
                    throw new ArgumentException("the encryped 文件4读密钥 must be Base64 encodeded format before get decrypting");
            }

            return output;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input">will be converted to UTF8 bytes and then for encryption</param>
        /// <param name="enableHash">if set to true, the raw input will be SHA256 firstly and then for encrypting, otherwise, the raw input will be used encrypting directly.</param>
        /// <returns></returns>
        public byte[] EncryptString(string input, bool enableHash = true)
        {
            if (string.IsNullOrEmpty(input)) throw new ArgumentNullException(nameof(input));
            return this.EncryptBytes(Encoding.UTF8.GetBytes(input), enableHash);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input">will be converted to UTF8 bytes and then encrypt</param>
        /// <returns>base64 encoded bytes</returns>
        public string EncryptStringToBase64(string input)
        {
            if (string.IsNullOrEmpty(input)) return null;
            return Convert.ToBase64String(this.EncryptString(input));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input">the raw input for encrypting</param>
        /// <param name="enableHash">if set to true, the raw input will be SHA256 firstly and then for encrypting, otherwise, the raw input will be used encrypting directly.</param>
        /// <returns></returns>
        public byte[] EncryptBytes(byte[] input, bool enableHash = true)
        {
            byte[] hashedInput = null;
            if (enableHash)
                using (SHA256 hash = SHA256Managed.Create())
                    hashedInput = hash.ComputeHash(input);
            else
                hashedInput = input;
            byte[] symmetricKey = this.GetEncryptionKey();
            using (MemoryStream ms = new MemoryStream())
            {
                using (Aes aes = Aes.Create())
                {
                    //byte[] symmetricKey = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    //    0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
                    aes.Key = symmetricKey;

                    byte[] iv = aes.IV;
                    ms.Write(iv, 0, iv.Length);

                    using (CryptoStream cryptoStream = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(hashedInput);
                    }
                }

                var encryptedBytes = ms.ToArray();//.Take(16).ToArray();
                this.logger.LogInformation($"RawBytes: 0x{input.ToHexLogString()} encrypted to 0x{encryptedBytes.ToHexLogString()}");
                return encryptedBytes;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input">the encrypted data</param>
        /// <returns>return the first 16 bytes from the decrypted data</returns>
        public byte[] Decrypt(byte[] input)
        {
            byte[] symmetricKey = this.GetEncryptionKey();
            return this.Decrypt(input, symmetricKey);
        }

        /// <summary>
        /// Decrypt the encrypted data by key. 
        /// </summary>
        /// <param name="input">the encrypted data</param>
        /// <param name="key">the key for decryption</param>
        /// <returns>the decrypted data</returns>
        public byte[] Decrypt(byte[] input, byte[] key)
        {
            byte[] symmetricKey = key;
            using (MemoryStream ms = new MemoryStream(input))
            {
                using (Aes aes = Aes.Create())
                {
                    byte[] iv = new byte[aes.IV.Length];
                    int numBytesToRead = aes.IV.Length;
                    int numBytesRead = 0;
                    while (numBytesToRead > 0)
                    {
                        int n = ms.Read(iv, numBytesRead, numBytesToRead);
                        if (n == 0) break;

                        numBytesRead += n;
                        numBytesToRead -= n;
                    }

                    //byte[] symmetricKey ={
                    //    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    //    0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};

                    using (CryptoStream cryptoStream = new CryptoStream(ms, aes.CreateDecryptor(symmetricKey, iv), CryptoStreamMode.Read))
                    {
                        using (MemoryStream outputMs = new MemoryStream())
                        {
                            cryptoStream.CopyTo(outputMs);
                            return outputMs.ToArray();//.Take(16).ToArray();
                        }
                        //using (var decryptReader = new Text(cryptoStream))
                        //{
                        //    string decryptedMessage = decryptReader.ReadToEnd();
                        //    Console.WriteLine($"The decrypted original message: {decryptedMessage}, base64 decode to: 0x{Convert.FromBase64String(decryptedMessage).ToHexLogString()}");
                        //    return Convert.FromBase64String(decryptedMessage);
                        //}
                    }
                }
            }
        }
    }
}