using CryptoPro.Security.Cryptography;
using CryptoPro.Security.Cryptography.X509Certificates;
using CryptoPro.Security.Cryptography.Xml;
using Hcs.Broker.Internal;
using System.Security;
using System.Security.Cryptography;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;

namespace Hcs.Broker.Api.Request
{
    /// <summary>
    /// Фильтр сообщений добавляет в XML-сообщение электронную подпись XADES/GOST
    /// </summary>
    internal class GostSigningMessageInspector(Client client) : IClientMessageInspector
    {
        private readonly Client client = client;

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            try
            {
                var filterHeader = "[Message Inspector]";

                PurgeDebuggerHeaders(ref request);

                var messageBody = GetMessageBodyString(ref request, Encoding.UTF8);
                if (!messageBody.Contains(Constants.SIGNED_XML_ELEMENT_ID))
                {
                    client.TryCaptureMessage(true, messageBody);
                }
                else
                {
                    client.TryLog($"{filterHeader} Signing message with key [{client.Certificate}]...");

                    var stopwatch = System.Diagnostics.Stopwatch.StartNew();

                    var xmlDocument = new XmlDocument();
                    xmlDocument.LoadXml(messageBody);

                    var provider = client.Certificate.PrivateKey as Gost3410_2012_256CryptoServiceProvider;
                    provider.SetContainerPassword(client.CertificatePin);

                    var signedXml = SignXmlFileXades(
                        xmlDocument,
                        client.Certificate,
                        provider,
                        CpSignedXml.XmlDsigGost3411_2012_256Url,
                        true);

                    stopwatch.Stop();

                    client.TryLog($"{filterHeader} Message signed in {stopwatch.ElapsedMilliseconds} ms");
                    client.TryCaptureMessage(true, signedXml);

                    request = Message.CreateMessage(
                        XmlReaderFromString(signedXml), int.MaxValue, request.Version);
                }
            }
            catch (System.Exception ex)
            {
                throw new System.Exception($"Exception occured in {GetType().Name}", ex);
            }

            return null;
        }

        private void PurgeDebuggerHeaders(ref Message request)
        {
            var limit = request.Headers.Count;
            for (var i = 0; i < limit; ++i)
            {
                if (request.Headers[i].Name.Equals("VsDebuggerCausalityData"))
                {
                    request.Headers.RemoveAt(i);

                    break;
                }
            }
        }

        private string GetMessageBodyString(ref Message request, Encoding encoding)
        {
            var mb = request.CreateBufferedCopy(int.MaxValue);
            request = mb.CreateMessage();

            var s = new MemoryStream();
            var xw = XmlWriter.Create(s);
            mb.CreateMessage().WriteMessage(xw);
            xw.Flush();

            s.Position = 0;

            var bXML = new byte[s.Length];
            s.Read(bXML, 0, (int)s.Length);

            if (bXML[0] != (byte)'<')
            {
                return encoding.GetString(bXML, 3, bXML.Length - 3);
            }
            else
            {
                return encoding.GetString(bXML, 0, bXML.Length);
            }
        }

        private string SignXmlFileXades(
            XmlDocument doc,
            CpX509Certificate certificate,
            AsymmetricAlgorithm key,
            string digestMethod,
            bool useDsPrefix)
        {
            var keyInfo = new CpKeyInfo();
            keyInfo.AddClause(new CpKeyInfoX509Data(certificate));

            var signedXml = new CpXadesSignedXml(doc)
            {
                SigningKey = key,
                KeyInfo = keyInfo
            };

            var reference = new CpReference()
            {
                Uri = "",
                DigestMethod = digestMethod
            };

            var signTransform = new CpXmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(signTransform);

            var c14nTransform = new CpXmlDsigC14NTransform();
            reference.AddTransform(c14nTransform);

            signedXml.AddReference(reference);

            if (useDsPrefix)
            {
                signedXml.SignatureNodePrefix = "ds";
            }

            signedXml.ComputeXadesSignature();

            var xmlDigitalSignature = signedXml.GetXml();
            doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));

            if (doc.FirstChild is XmlDeclaration)
            {
                doc.RemoveChild(doc.FirstChild);
            }

            return doc.OuterXml;
        }

        private XmlReader XmlReaderFromString(string xml)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(xml);
            writer.Flush();

            stream.Position = 0;

            return XmlReader.Create(stream);
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            client.TryCaptureMessage(false, reply.ToString());
        }
    }
}
