/* This is free and unencumbered software released into the public domain. */

import { Config } from './config.js';
import { SkeletonServer } from './servers/skeleton.js';
import { DatabaseServer } from './servers/database.js';
import { Request } from './request.js';
import { ExpectedError } from './errors.js';

import { bytesToHex } from '@aurora-is-near/engine';
import { Engine } from '@aurora-is-near/engine';
import jayson from 'jayson';
import { Logger } from 'pino';

interface MethodMap {
  [methodName: string]: jayson.MethodLike;
}

export class Method extends jayson.Method {
  public readonly server;

  constructor(
    public handler?: jayson.MethodHandlerType,
    options?: jayson.MethodOptions
  ) {
    super(handler, {});
    this.server = options?.useContext as any; // HACK
  }

  getHandler(): jayson.MethodHandlerType {
    return this.handler!;
  }

  setHandler(handler: jayson.MethodHandlerType) {
    this.handler = handler;
  }

  execute(
    server: jayson.Server,
    requestParams: jayson.RequestParamsLike,
    request: any, // context
    callback: jayson.JSONRPCCallbackType
  ): any {
    const args = (requestParams || []) as any[];
    const result: Promise<any> = (this.handler as any).call(
      this.server,
      new Request(request),
      ...args
    );
    result
      .then((value: any) => (callback as any)(undefined, value))
      .catch((error: Error) => {
        const metadata = {
          host: request?.headers?.host || undefined,
          'cf-ray': request?.headers['cf-ray'] || undefined,
          'cf-request-id': request?.headers['cf-request-id'] || undefined,
        };
        if (error instanceof ExpectedError) {
          return (callback as any)(
            server.error(
              error.code,
              error.message,
              error.data
                ? ((bytesToHex(error.data) as unknown) as undefined)
                : {
                    ...metadata,
                    ...{ request_body: request.body },
                  }
            )
          );
        }
        if (this.server?.config?.debug) {
          console.error(error);
        }
        if (this.server?.logger) {
          this.server.logger.error(error);
        }
        return (callback as any)(
          server.error(
            -32603,
            `Internal Error - ${error.message}. Please report a bug at <https://github.com/aurora-is-near/aurora-relayer/issues>`,
            {
              ...metadata,
              ...{ request_body: request.body },
            }
          )
        );
      });
    return null;
  }
}

export function createServer(
  config: Config,
  logger: Logger,
  engine: Engine
): jayson.Server {
  const server = new DatabaseServer(config, logger, engine);
  const methodList = Object.getOwnPropertyNames(SkeletonServer.prototype)
    .filter((id: string) => id !== 'constructor' && id[0] != '_')
    .map((id: string) => [id, (server as any)[id]]);
  const methodMap: MethodMap = Object.fromEntries(methodList);
  const jaysonServer = new jayson.Server(methodMap, {
    methodConstructor: Method,
    useContext: server as any,
  });
  return jaysonServer;
}