using GenHTTP.Api.Content.Authentication;
using InABox.Clients;
using InABox.Core;
using Microsoft.Exchange.WebServices.Data;
using NPOI.SS.Formula.Functions;

namespace InABox.Rpc
{
    public abstract class RpcCommandHandler<TSender, TCommand, TParameters, TResult> : IRpcCommandHandler 
        where TSender : class
        where TCommand : IRpcCommand<TParameters, TResult>
        where TParameters : IRpcCommandParameters, new()
        where TResult : IRpcCommandResult
    {
        public TSender Sender { get; }
        
        private static readonly bool IsLogCommand = typeof(TCommand).IsAssignableTo(typeof(IRpcLogCommand));

        public RpcCommandHandler(TSender sender)
        {
            Sender = sender ?? throw new ArgumentNullException(nameof(sender));
        }

        protected abstract TResult Execute(IRpcSession session, TParameters parameters, Logger logger);

        public byte[] Execute(IRpcSession session, byte[] payload, Logger logger)
        {
            var start = DateTime.Now;
            var parameters = Serialization.ReadBinary<TParameters>(payload, BinarySerializationSettings.Latest);
            if (IsLogCommand)
                logger.Info(session.UserID, $"[{session.Platform} {session.Version}] {parameters.ShortDescription()}: {parameters.FullDescription()}");
            
            try
            {
                var result = Execute(session, parameters, logger);

                if (IsLogCommand)
                {
                    logger.Info(session.UserID, 
                        string.Format(
                            "[{0} {1}] [{2:D8}] {3} Complete: {4}",
                            session.Platform, session.Version,
                            (int)DateTime.Now.Subtract(start).TotalMilliseconds, 
                            parameters.ShortDescription(),
                            result.FullDescription()
                        )
                    );
                }

                return Serialization.WriteBinary(result, BinarySerializationSettings.Latest);
            }
            catch (RpcException)
            {
                throw;
            }
            catch (Exception e)
            {
                logger.Info(session.UserID, string.Format("[{0} {1}] [{2:D8}] {3} failed: {4}\n\n{5}",
                    session.Platform,
                    session.Version,
                    (int)DateTime.Now.Subtract(start).TotalMilliseconds,
                    parameters.ShortDescription(),
                    e.Message,
                    e.StackTrace));
                throw new RpcException(e.Message, RpcError.SERVERERROR);
            }
        }
    }
}