using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Grpc.Core;
using Org.BouncyCastle.OpenSsl;
using Rpcscanning;
using RpcScannerClient.Events;
using Valg.EVA.Skanning.Scan.ReadSoftEmulation.Events.Rpc;
using Valg.EVA.Skanning.Tools;
using Org.BouncyCastle.Security;
using Valg.EVA.Skanning.Resources.Language;

namespace Valg.EVA.Skanning.Scan.ReadSoftEmulation.ScannerAccess
{
    public class RpcClient : IDisposable
    {
        private readonly Channel _channel;
        private readonly RpcScanner.RpcScannerClient _client;

        public delegate void DocumentScannedEventHandler(object sender, RpcDocumentScannedEventArgs e);
        public event DocumentScannedEventHandler DocumentScanned;

        public delegate void FeederEmptyEventHandler(object sender, RpcFeederEmptyEventArgs e);
        public event FeederEmptyEventHandler FeederEmpty;

        public delegate void BatchAbortedEventHandler(object sender, RpcBatchAbortedEventArgs e);
        public event BatchAbortedEventHandler BatchAborted;

        public delegate void ScanningErrorEventHandler(object sender, RpcScanningErrorEventArgs e);
        public event ScanningErrorEventHandler Error;

        private AsyncServerStreamingCall<RpcMessage> _scanStream;

        private int _sessionDocumentScannedCounter = 0;
        private readonly DataProtectionApi _dataProtectionApi = new DataProtectionApi();



        public RpcClient()
        {
            var options = new List<ChannelOption>();
            options.Add(new ChannelOption("grpc.max_receive_message_length", (10*1024)*1024));
            _channel = new Channel("localhost:50051", GetOpensslCredentials(),options);
            _client = new RpcScanner.RpcScannerClient(_channel);
            MonitorChannelStatus();
        }

        private static SslCredentials GetOpensslCredentials()
        {
            X509Store store = new X509Store(StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);
            X509Certificate2 clientCertificate = null;
            X509Certificate2 certificateAuthority = null;
            foreach (var certificate in store.Certificates)
            {
                if (certificate.HasPrivateKey && certificate.FriendlyName.Equals("EVA Skanning RPC client", StringComparison.InvariantCultureIgnoreCase))
                {
                    clientCertificate = certificate;
                }

                if (certificate.SubjectName.Name.Equals("CN=EVASkanningRpcCA, OU=EVASkanning", StringComparison.InvariantCultureIgnoreCase))

                {
                    certificateAuthority = certificate;
                }
            }

            if (clientCertificate == null)
            {
                MessageBox.Show(ScanLanguageString.RpcClient_UnableToFindClientCertificate_Error_Description, ScanLanguageString.RpcClient_UnableToFindClientCertificate_Error_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }

            var privateKey = ExportPrivateKeyToPem(clientCertificate);
            var publicKey = ExportPublicKeyToPem(clientCertificate);
            var certificateAuthorityPublicKey = ExportPublicKeyToPem(certificateAuthority);
            return new SslCredentials(certificateAuthorityPublicKey, new KeyCertificatePair(publicKey, privateKey));
        }

        private static string ExportPublicKeyToPem(X509Certificate2 cert)
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendLine("-----BEGIN CERTIFICATE-----");
            builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
            builder.AppendLine("-----END CERTIFICATE-----");

            return builder.ToString();
        }


        private static string ExportPrivateKeyToPem(X509Certificate2 cert)
        {
            var pkey = (RSACryptoServiceProvider)cert.PrivateKey;
            var keyPair = DotNetUtilities.GetRsaKeyPair(pkey);
            StringBuilder builder = new StringBuilder();

            var memoryStream = new MemoryStream();
            using (TextWriter tw = new StreamWriter(memoryStream))
            {
                PemWriter pw = new PemWriter(tw);
                pw.WriteObject(keyPair.Private);
                tw.Flush();
            }

            return Encoding.ASCII.GetString(memoryStream.ToArray());
        }


        private async Task MonitorChannelStatus()
        {
            while (IsChannelStatusOk(_channel.State))
            {
                await Task.Delay(500);
            }
            Error?.Invoke(this,new RpcScanningErrorEventArgs() { ErrorCode = -1, ErrorText = $"Unable to communicate with RPC server. State: {_channel.State}"});
        }

        private bool IsChannelStatusOk(ChannelState state)
        {
            return (_channel.State == ChannelState.Idle || _channel.State == ChannelState.Connecting || _channel.State == ChannelState.Ready);
        }

        public async Task Scan()
        {
            _sessionDocumentScannedCounter = 0;
            try
            {
                _scanStream = _client.Scan(new Empty());
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Error?.Invoke(this, new RpcScanningErrorEventArgs() {ErrorCode = -1, ErrorText = e.Message});
            }

            try
            {
                while (await _scanStream.ResponseStream.MoveNext())
                {
                    var rpcMessage = _scanStream.ResponseStream.Current;
                    if (rpcMessage?.EventType == EventType.NewScannedDocument && rpcMessage?.Document != null)
                    {
                        if (IsHashValid(rpcMessage.Document.FrontImage.ToByteArray(), rpcMessage.Document.FrontImageHash)
                            && IsHashValid(rpcMessage.Document.BackImage.ToByteArray(), rpcMessage.Document.BackImageHash))
                        {
                            DocumentScanned?.Invoke(this, new RpcDocumentScannedEventArgs()
                            {
                                Document = _scanStream.ResponseStream.Current.Document
                            });
                            _sessionDocumentScannedCounter++;
                        }
                        else
                        {
                            Error?.Invoke(this, new RpcScanningErrorEventArgs() {ErrorCode = 9999, ErrorText = "The image bytes and expected hash doesn't match"});
                        }
                    }

                    if (rpcMessage?.EventType == EventType.FeederEmpty)
                    {
                        FeederEmpty?.Invoke(this, new RpcFeederEmptyEventArgs());
                    }

                    if (rpcMessage?.EventType == EventType.Exception && !String.IsNullOrEmpty(rpcMessage?.ErrorMessage))
                    {
                        Error?.Invoke(this, new RpcScanningErrorEventArgs() {ErrorCode = rpcMessage.ErrorCode, ErrorText = rpcMessage.ErrorMessage});
                    }
                }
            }

            catch (RpcException ex)
            {
                Error?.Invoke(this,new RpcScanningErrorEventArgs() { ErrorCode = -1, ErrorText = ex.Message});
            }
        }

        private bool IsHashValid(byte[] imageData, string hash)
        {
            var inputHash = _dataProtectionApi.Decrypt(hash);
            var calculatedHash = ComputeHash(imageData);
            return inputHash.Equals(calculatedHash);
        }

        private string ComputeHash(byte[] bytes)
        {
            using (var sha256 = new SHA256CryptoServiceProvider())
            {
                return Convert.ToBase64String(sha256.ComputeHash(bytes));
            }
        }

        public void Dispose()
        {
            try
            {
                _client?.ReleaseScanner(new Empty());
            }
            catch (Exception)
            {
                
            }
            _channel.ShutdownAsync().Wait();
        }

        public void InitializeScanner(string driverDescription, string configurationFile)
        {
            try
            {
                _client.InitializeScanner(new ScannerConfiguration() { DriverDescription = driverDescription, ConfigFile = configurationFile });
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Error?.Invoke(this,new RpcScanningErrorEventArgs() { ErrorCode = -1, ErrorText = e.Message});
            }
        }


        internal void StopScanning()
        {
            _client?.StopScanning(new Empty());
        }


        public string[] GetInstalledScanners()
        {
            var rpcResult = _client.GetInstalledScanners(new Empty());
            return new List<string>(rpcResult.DriverDescriptions).ToArray();
        }

        public string[] GetConnectedScanners()
        {
            var rpcResult = _client.GetConnectedScanners(new Empty());
            return new List<string>(rpcResult.DriverDescriptions).ToArray();
        }

        public void ReleaseScanner()
        {
            _client.ReleaseScanner(new Empty());
        }

        public void ConfigureViaGui(IntPtr hwnd)
        {
            _client.ConfigureViaGui(new Hwnd() { Handle = (int)hwnd });
        }
    }
}

