namespace Kit.Helpers
{
    using System;
    using System.Linq;
    using System.IO;
    using System.Security.Cryptography;
    using System.IO.Compression;

    public static class RSAService
    {
        public static byte[] RSAEncrypt(this byte[] dataToEncrypt, string privateKey, bool DoOAEPPadding = false)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.FromXmlString(privateKey);

                using (var fstreamOut = new MemoryStream())
                {
                    int i = 0;
                    int sizebuf = 64;
                    byte[] buf = new byte[sizebuf];
                    do
                    {
                        buf = dataToEncrypt.Skip(i * sizebuf).Take(sizebuf).ToArray();
                        if (buf.Length == 0) break;
                        byte[] encrypted = rsa.Encrypt(buf, DoOAEPPadding);
                        fstreamOut.Write(encrypted, 0, encrypted.Length);
                        i++;
                    } while (buf.Length == sizebuf);

                    fstreamOut.Position = 0;
                    return fstreamOut.ReadAllBytes();
                }
            }
        }

        public static byte[] RSADecrypt(this byte[] dataToDecrypt, string publickey, bool DoOAEPPadding = false)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.FromXmlString(publickey);

                using (var fstreamOut = new MemoryStream())
                {
                    int i = 0;
                    int sizebuf = 128;
                    byte[] buf = new byte[sizebuf];
                    do
                    {
                        buf = dataToDecrypt.Skip(i * sizebuf).Take(sizebuf).ToArray();
                        if (buf.Length == 0) break;
                        byte[] encrypted = rsa.Decrypt(buf, DoOAEPPadding);
                        fstreamOut.Write(encrypted, 0, encrypted.Length);
                        i++;
                    } while (buf.Length == sizebuf);

                    fstreamOut.Position = 0;
                    return fstreamOut.ReadAllBytes();
                }
            }
        }

        public static byte[] RSAGetSignature(this byte[] data, string privedkey)
        {

            using (RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider())
            {
                rsaCSP.FromXmlString(privedkey);
                SHA256Managed hash = new SHA256Managed();
                byte[] hashedData;

                hashedData = hash.ComputeHash(data);
                return rsaCSP.SignHash(hashedData, CryptoConfig.MapNameToOID("SHA256"));
            }
        }

        public static bool RSAVerifySignature(this byte[] signedData, string publickey, byte[] signature)
        {
            using (RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider())
            {
                rsaCSP.FromXmlString(publickey);
                SHA256Managed hash = new SHA256Managed();
                byte[] hashedData;

                bool dataOK = rsaCSP.VerifyData(signedData, CryptoConfig.MapNameToOID("SHA256"), signature);
                hashedData = hash.ComputeHash(signedData);
                return rsaCSP.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA256"), signature);
            }
        }
        public static Stream GetPackedSignature(this Stream stream, string privedkey)
        {
            return stream.ReadAllBytes().GetPackedSignature(privedkey);
        }

        public static Stream GetPackedSignature(this byte[] data, string privedkey)
        {
            byte[] signature = data.RSAGetSignature(privedkey);
            Stream packed = new MemoryStream();

            using (var zipObj = new ZipArchive(packed, ZipArchiveMode.Create,true))
            {
                ZipArchiveEntry entryData = zipObj.CreateEntry("file.dat");
                using (var entryStream = entryData.Open())
                {
                    entryStream.Write(data, 0, data.Length);
                    entryStream.Flush();
                }

                ZipArchiveEntry entrySignature = zipObj.CreateEntry("signature.key");
                using (var entryStream = entrySignature.Open())
                {
                    entryStream.Write(signature, 0, signature.Length);
                    entryStream.Flush();
                }

            }

            return packed;
        }

        public static bool VerifyPackedSignature(this byte[] arhiveData, string publickey)
        {
            using (ZipArchive archive = new ZipArchive(new MemoryStream(arhiveData), ZipArchiveMode.Read))
            {
                //если нет файла подписи и файла данных (нарушен формат пакета) то проверка отрицательная
                if (
                    !archive.Entries.Any(x => x.FullName.EndsWith("signature.key"))
                    ||
                    !archive.Entries.Any(x => x.FullName.EndsWith("file.dat"))
                    ) return false;

                byte[] signature = new byte[] { };
                byte[] data = new byte[] { };

                foreach (ZipArchiveEntry entry in archive.Entries)
                {
                    if (entry.FullName.EndsWith("signature.key", StringComparison.OrdinalIgnoreCase))
                    {
                        signature = entry.Open().ReadAllBytes();
                    }

                    if (entry.FullName.EndsWith("file.dat", StringComparison.OrdinalIgnoreCase))
                    {
                        data = entry.Open().ReadAllBytes();
                    }
                }

                if (signature.Length == 0 || data.Length == 0) return false;

                return RSAVerifySignature(data, publickey, signature);
            }

        }

        public static byte[] ReadPackedWithSignature(this byte[] arhiveData, string publickey)
        {
            return new MemoryStream(arhiveData).ReadPackedWithSignature(publickey);
        }
        public static byte[] ReadPackedWithSignature(this Stream arhiveStream, string publickey)
        {
            using (ZipArchive archive = new ZipArchive(arhiveStream, ZipArchiveMode.Read))
            {
                byte[] signature = new byte[] { };
                byte[] data = new byte[] { };

                foreach (ZipArchiveEntry entry in archive.Entries)
                {
                    if (entry.FullName.EndsWith("signature.key", StringComparison.OrdinalIgnoreCase))
                    {
                        signature = entry.Open().ReadAllBytes();
                    }

                    if (entry.FullName.EndsWith("file.dat", StringComparison.OrdinalIgnoreCase))
                    {
                        data = entry.Open().ReadAllBytes();
                    }
                }

                if (signature.Length == 0 || data.Length == 0) return null;

                if(RSAVerifySignature(data, publickey, signature))
                {
                    return data;
                }
            }
            return null;
        }
    }
}

