//@template-ignore
import { VerificationError } from "./ts_base";
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";


export class Service<T> {
   public name: string = null as any;
   public functions = new Set<string>();

   constructor() {}
}

type ISendMessageCB = (data: any, catchedErr?: Error) => void;

export class ServiceProvider<T = any> {
   services = new Map<string, Service<T>>();
   addService(service: Service<T>) {
      this.services.set(service.name, service);
      Logging.log("SERVER: Adding Service to provider:", service.name);
      Logging.log("SERVER: Service provides:", [...service.functions.keys()])
   }

   getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
      return new Session(this, send, ctx);
   }
}

class Session<T> {
   ctx: Partial<T>;

   constructor(
      private provider: ServiceProvider,
      private _send: ISendMessageCB,
      ctx?: Partial<T>
   ) {
      this.ctx = ctx || {};
   }

   send(data: any, catchedErr?:Error) {
      Logging.log("SERVER: Sending Message", data)
      this._send(data, catchedErr);
   }

   async onMessage(data: RequestObject) {
      Logging.log("SERVER: Received Message", data);
      try {
         if (!data.method) {
            if (data.id) {
               this.send({
                  jsonrpc: "2.0",
                  id: data.id,
                  error: {
                     code: ErrorCodes.InvalidRequest,
                     message: "No method defined!",
                  },
               } as ResponseObject);
            }
            return;
         }

         const [srvName, fncName] = data.method.split(".");
         Logging.log("SERVER: Message for", srvName, fncName);

         const service = this.provider.services.get(srvName);
         if (!service) {
            Logging.log("SERVER: Did not find Service");
            if (data.id) {
               this.send({
                  jsonrpc: "2.0",
                  id: data.id,
                  error: {
                     code: ErrorCodes.MethodNotFound,
                     message: "Service not found!",
                  },
               } as ResponseObject);
            }
            return;
         }

         const fnc = service.functions.has(fncName);
         if (!fnc) {
            Logging.log("SERVER: Did not find Function");
            if (data.id) {
               this.send({
                  jsonrpc: "2.0",
                  id: data.id,
                  error: {
                     code: ErrorCodes.MethodNotFound,
                     message: "Function not found!",
                  },
               } as ResponseObject);
            }
            return;
         }

         let result = await (service as any)["_" + fncName](data.params, this.ctx);
         if(data.id) { //Request
            this.send({
               jsonrpc: "2.0",
               id: data.id,
               result: result,
            } as ResponseObject);
         } //else Notification and response is ignored
      } catch (err) {
         if (data.id) {
            this.send(
               {
                  jsonrpc: "2.0",
                  id: data.id,
                  error: {
                     code: ErrorCodes.InternalError,
                     message: err.message,
                     data: err instanceof VerificationError ? {
                        $: "verification_error",
                        type: err.type,
                        field: err.field,
                        value: err.value
                     } : {
                        $: "unknown_error"
                     },
                  },
               } as ResponseObject,
               err
            );
         }
         //TODO: Think about else case
      }
   }
}
