using Hcs.Client.Internal;
using GostXades;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;

namespace Hcs.Client.Api.Request
{
    /// <summary>
    /// Фильтр сообщений добавляет в XML-сообщение электронную подпись XADES/GOST
    /// </summary>
    internal class GostSigningMessageInspector(ClientBase client) : IClientMessageInspector
    {
        private readonly ClientBase 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
                {
                    var certInfo = X509Tools.GetFullnameWithExpirationDateStr(client.Certificate);
                    client.TryLog($"{filterHeader} signing message with key [{certInfo}]...");

                    var stopwatch = System.Diagnostics.Stopwatch.StartNew();
                    var service = new GostXadesBesService(client.CryptoProviderType);
                    var signedXml = service.Sign(messageBody,
                        Constants.SIGNED_XML_ELEMENT_ID,
                        client.CertificateThumbprint,
                        client.CertificatePassword);
                    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 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());
        }
    }
}
