using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace __NAMESPACE__;

public class JRpcServer<TContext>
{
   public IDictionary<string, JRpcService<TContext>> Services;

   public JRpcServer()
   {
      this.Services = new Dictionary<string, JRpcService<TContext>>();
   }

   public void AddService(JRpcService<TContext> service)
   {
      this.Services.Add(service.Name, service);
   }

   public JRpcServerSession<TContext> GetSession(JRpcTransport transport, TContext context)
   {
      return new JRpcServerSession<TContext>(this, transport, context);
   }
}

public class JRpcServerSession<TContext>
{
   public JRpcServer<TContext> Server { get; private set; }
   public JRpcTransport Transport { get; private set; }
   public TContext Context { get; private set; }

   public JRpcServerSession(JRpcServer<TContext> server, JRpcTransport transport, TContext context)
   {
      this.Server = server;
      this.Transport = transport;
      this.Context = context;

      this.Transport.OnPacket += this.HandlePacket;
   }

   private void HandlePacket(string packet)
   {
      try
      {
         var parsed = JsonNode.Parse(packet);
         if (parsed == null || (string?)parsed["jsonrpc"] != "2.0")
            return;

         if (parsed["method"] != null) // Request or Notification
         {
            var id = (string?)parsed["id"];
            var splitted = ((string)parsed["method"]!).Split(".", 2);
            var serviceName = splitted[0];
            var functionName = splitted[1];

            if (serviceName == null || functionName == null)
            {
               if (id != null)
               {
                  var response = new JsonObject
                  {
                     ["jsonrpc"] = "2.0",
                     ["id"] = id,
                     ["error"] = new JsonObject
                     {
                        ["code"] = -32700,
                        ["message"] = "Method not found!",
                     }
                  };
                  _ = this.Transport.Write(response.ToJsonString()!);
               }
               return;
            }

            var service = this.Server.Services[serviceName];
            if (service == null)
            {
               if (id != null)
               {
                  var response = new JsonObject
                  {
                     ["jsonrpc"] = "2.0",
                     ["id"] = id,
                     ["error"] = new JsonObject
                     {
                        ["code"] = -32700,
                        ["message"] = "Method not found!",
                     }
                  };
                  _ = this.Transport.Write(response.ToJsonString()!);
               }
               return;
            }

            if (!service.Functions.Contains(functionName))
            {
               if (id != null)
               {
                  var response = new JsonObject
                  {
                     ["jsonrpc"] = "2.0",
                     ["id"] = id,
                     ["error"] = new JsonObject
                     {
                        ["code"] = -32700,
                        ["message"] = "Method not found!",
                     }
                  };
                  _ = this.Transport.Write(response.ToJsonString()!);
               }
               return;
            }

            if (!(parsed["params"] is JsonArray || parsed["params"] is JsonObject))
            {
               if (id != null)
               {
                  var response = new JsonObject
                  {
                     ["jsonrpc"] = "2.0",
                     ["id"] = id,
                     ["error"] = new JsonObject
                     {
                        ["code"] = -32602,
                        ["message"] = "Invalid Parameters!",
                     }
                  };
                  _ = this.Transport.Write(response.ToJsonString()!);
               }
               return;
            }

            _ = service.HandleRequest(functionName, parsed["params"]!, this.Context).ContinueWith(result =>
            {
               if (id != null)
               {
                  var response = new JsonObject
                  {
                     ["jsonrpc"] = "2.0",
                     ["id"] = id,
                     ["error"] = result.IsFaulted ? new JsonObject
                     {
                        ["code"] = -32603,
                        ["message"] = result.Exception!.InnerException!.Message
                     } : null,
                     ["result"] = !result.IsFaulted ? result.Result : null,
                  };
                  _ = this.Transport.Write(response.ToJsonString()!);
               }
            });


            //TODO: implement Notifications
         }
         else
         {
            // Ignoring everyting else. Don't care!
            return;
         }
      }
      catch (Exception)
      {
         //TODO: Maybe log exception, but don't break!
      }
   }


   public async void SendNotification(string method, JsonArray param)
   {
      var not = new JsonObject
      {
         ["jsonrpc"] = "2.0",
         ["method"] = method,
         ["params"] = param,
      };

      await this.Transport.Write(not.ToJsonString());
   }
}

public abstract class JRpcService<TContext>
{
   public abstract string Name { get; }

   public HashSet<string> Functions = new HashSet<string>();


   protected void RegisterFunction(string name)
   {
      this.Functions.Add(name);
   }

   public abstract Task<JsonNode?> HandleRequest(string function, JsonNode param, TContext context);
   // public abstract Task HandleNotification(string notification, JsonNode param);
}
