using System.IO.Pipes;
using System.Security.Principal;
using H.Formatters;
using H.Pipes;
using H.Pipes.AccessControl;

namespace InABox.Rpc
{
    public class RpcServerPipeTransport : RpcServerTransport<PipeConnection<RpcMessage?>>, IDisposable
    {
        private PipeServer<RpcMessage?> _transport;

        public override bool IsSecure() => false;
        
        private void SetPipeSecurity()
        {

            #pragma warning disable CA1416
            
            var pipeSecurity = new PipeSecurity();

            pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
            pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
            pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));

            _transport.SetPipeSecurity(pipeSecurity);
            
            #pragma warning restore CA1416

        }

        public RpcServerPipeTransport(string name)
        {
            _transport = new PipeServer<RpcMessage?>(name, formatter:new BinaryFormatter());

            #if WINDOWS
            SetPipeSecurity();
            #endif
            
            _transport.ClientConnected += Transport_OnConnected;
            _transport.ClientDisconnected += Transport_OnDisconnected;
            _transport.MessageReceived += Transport_OnMessage;
            _transport.ExceptionOccurred += Transport_OnException;
        }
        
        public override void Start()
        {
            _transport.StartAsync().Wait();
        }

        public override void Stop()
        {
            _transport.StopAsync().Wait();
        }
        
        private void Transport_OnConnected(object? sender, H.Pipes.Args.ConnectionEventArgs<RpcMessage?> e)
        {
            DoOpen(e.Connection);
        }

        private void Transport_OnMessage(object? sender, H.Pipes.Args.ConnectionMessageEventArgs<RpcMessage?> e)
        {
            Task.Run(() =>
            {
                var response = DoMessage(e.Connection, e.Message);
                e.Connection.WriteAsync(response);
            });
        }

        public override void Send(PipeConnection<RpcMessage?> connection, RpcMessage message)
        {
            connection.WriteAsync(message);
        }

        private void Transport_OnDisconnected(object? sender, H.Pipes.Args.ConnectionEventArgs<RpcMessage?> e)
        {
            DoClose(e.Connection, RpcTransportCloseEventType.Closed);
            e.Connection.DisposeAsync();
        }

        private void Transport_OnException(object? sender, H.Pipes.Args.ExceptionEventArgs e)
        {
            DoException(null, e.Exception);
        }

        public void Dispose()
        {
            _transport.DisposeAsync().AsTask().Wait();
        }
        
        ~RpcServerPipeTransport()
        {
            Dispose();
        }
    }
}