using NetMQ;
using Phantom.Utils.Rpc.Message;
using Phantom.Utils.Tasks;
using Serilog;

namespace Phantom.Utils.Rpc;

static class RpcRuntime {
	private static bool HasRuntime { get; set; }

	internal static void MarkRuntimeCreated() {
		if (HasRuntime) {
			throw new InvalidOperationException("Only one instance of RpcRuntime can be created.");
		}
		
		HasRuntime = true;
	}

	internal static void SetDefaultSocketOptions(ThreadSafeSocketOptions options) {
		// TODO test behavior when either agent or server are offline for a very long time
		options.DelayAttachOnConnect = true;
		options.ReceiveHighWatermark = 10_000;
		options.SendHighWatermark = 10_000;
	}
}

public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket, new() {
	private readonly TSocket socket;
	private readonly ILogger runtimeLogger;
	private readonly MessageReplyTracker replyTracker;
	private readonly TaskManager taskManager;

	protected RpcRuntime(RpcConfiguration configuration, TSocket socket) {
		RpcRuntime.MarkRuntimeCreated();
		RpcRuntime.SetDefaultSocketOptions(socket.Options);
		this.socket = socket;
		this.runtimeLogger = configuration.RuntimeLogger;
		this.replyTracker = new MessageReplyTracker(runtimeLogger);
		this.taskManager = new TaskManager(configuration.TaskManagerLogger);
	}

	protected async Task Launch() {
		Connect(socket);

		void RunTask() {
			try {
				Run(socket, replyTracker, taskManager);
			} catch (Exception e) {
				runtimeLogger.Error(e, "Caught exception in RPC thread.");
			}
		}
		
		try {
			await Task.Factory.StartNew(RunTask, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
		} catch (OperationCanceledException) {
			// ignore
		} finally {
			await taskManager.Stop();
			await Disconnect();
			
			socket.Dispose();
			NetMQConfig.Cleanup();
			runtimeLogger.Information("ZeroMQ client stopped.");
		}
	}
	
	protected abstract void Connect(TSocket socket);
	protected abstract void Run(TSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager);
	
	protected virtual Task Disconnect() {
		return Task.CompletedTask;
	}
}