using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Kit.Sign
{
    [Obfuscation(Exclude = true, ApplyToMembers = true, StripAfterObfuscation = true)]
    public interface ISignService
    {
        void Sign(Stream P_0, Stream P_1);
        void Verify(Stream P_0, Stream P_1, out bool P_2);
    }

    [Obfuscation(Exclude = true, ApplyToMembers = false, StripAfterObfuscation = true)]
    public class SignService : ISignService
    {
        private SignServicePrivate _service;
        public SignService(string? privateKeyString, string? publicKeyString, string? password)
        {
            _service = new SignServicePrivate(privateKeyString, publicKeyString, password);
        }

        public void Sign(Stream dataStream, Stream signatureStream) => _service.Sign(dataStream, signatureStream);
        public void Verify(Stream dataStream, Stream signatureStream, out bool isValid) => isValid = _service.Verify(dataStream, signatureStream);
    }

    internal class SignServicePrivate
    {
        private string? _privateKeyString;
        private string? _publicKeyString;
        private string? _password;
        private readonly HashAlgorithmName _hashAlgorithmName;
        private readonly RSASignaturePadding _signaturePadding;
        public SignServicePrivate(string? privateKeyString, string? publicKeyString, string? password)
        {
            _privateKeyString = privateKeyString;
            _publicKeyString = publicKeyString;
            _password = password;
            _hashAlgorithmName = HashAlgorithmName.SHA256;
            _signaturePadding = RSASignaturePadding.Pkcs1;
        }

        private ReadOnlySpan<byte> GetPrivateKey() => Convert.FromBase64String(_privateKeyString.Split("-", StringSplitOptions.RemoveEmptyEntries)[1]);
        private ReadOnlySpan<byte> GetPublicKey() => Encoding.UTF8.GetBytes(_publicKeyString);

        private RSA ImportPrivate(RSA rsa)
        {
            if (_privateKeyString == null || _privateKeyString.Length == 0) throw new InvalidOperationException($"{nameof(ImportPrivate)} is unavailable");
            if (string.IsNullOrWhiteSpace(_password) == false)
                rsa.ImportEncryptedPkcs8PrivateKey(_password, GetPrivateKey(), out _);
            else
                rsa.ImportPkcs8PrivateKey(GetPrivateKey(), out _);

            return rsa;
        }

        public void Sign(Stream dataStream, Stream signatureStream)
        {
            if (_privateKeyString == null || _privateKeyString.Length == 0) throw new InvalidOperationException("Sign is unavailable");
            using (RSA rsa = ImportPrivate(RSA.Create()))
            {
                WriteSignatureBytes(signatureStream, rsa.SignData(ReadAllBytes(dataStream), _hashAlgorithmName, _signaturePadding));
            }
        }

        public bool Verify(Stream dataStream, Stream signatureStream)
        {
            if (_publicKeyString == null || _publicKeyString.Length == 0) throw new InvalidOperationException("Verify is unavailable");
            using var publicX509 = new X509Certificate2(GetPublicKey());
            using (var rsa = publicX509.GetRSAPublicKey()!)
            {
                return rsa.VerifyData(ReadAllBytes(dataStream), ReadSignatureBytes(signatureStream), _hashAlgorithmName, _signaturePadding);
            }
        }

        private void WriteSignatureBytes(Stream signatureStream, byte[] signatureBytes)
        {
            byte[] base64Signature = Encoding.UTF8.GetBytes(Convert.ToBase64String(signatureBytes));
            signatureStream.Write(base64Signature, 0, base64Signature.Length);
        }

        private byte[] ReadSignatureBytes(Stream signatureStream)
        {
            long position = 0;
            if (signatureStream.CanSeek)
            {
                position = signatureStream.Position;
            }
            string base64Signature = string.Empty;

            using (TextReader textReader = new StreamReader(signatureStream, leaveOpen: true))
            {
                base64Signature = textReader.ReadToEnd();
            }

            if (signatureStream.CanSeek)
            {
                signatureStream.Position = position;
            }

            return Convert.FromBase64String(base64Signature);
        }

        private static byte[] ReadAllBytes(Stream instream)
        {
            if (instream is MemoryStream)
                return ((MemoryStream)instream).ToArray();

            using (var memoryStream = new MemoryStream())
            {
                instream.CopyTo(memoryStream);
                return memoryStream.ToArray();
            }
        }
    }
}
