import { IEvent, IEventClass, IRequest, IRequestClass } from "@uxland/harmonix";
import { Mediator, mediatorSettings, notificationHandler, requestHandler } from "mediatr-ts";
import { BROKER_EVENTS } from "./broker-events";
import { type PrimariaBroker, type BrokerDisposableHandler } from "./primaria-broker";

type messageHandler = (payload: unknown) => unknown | Promise<unknown>;

const disposableFactory = (handler: any) => {
  return {
    dispose: () => {
      mediatorSettings.resolver.remove(handler.name);
      const mappingIndex = mediatorSettings.dispatcher.notifications._mappings.findIndex(
        (mapping) => mapping.handler.name === handler.name,
      );
      mappingIndex > -1 &&
        mediatorSettings.dispatcher.notifications._mappings.splice(mappingIndex, 1);
    },
  };
};
class Broker implements PrimariaBroker {
  public readonly events = BROKER_EVENTS;
  private mediator: Mediator;
  constructor() {
    this.mediator = new Mediator();
    mediatorSettings.resolver.clear();
  }
  // biome-ignore lint/complexity/noBannedTypes: <explanation>
  private eventConstructorMap: Map<string, Function> = new Map();

  // biome-ignore lint/complexity/noBannedTypes: <explanation>
  private requestConstructorMap: Map<string, Function> = new Map();

  send<TRequest extends IRequest<TResponse>, TResponse>(message: TRequest): Promise<TResponse>;
  send<TPayload, TResponse>(requestName: string, payload: TPayload): Promise<TResponse>;
  send<TResponse>(request: IRequest<TResponse> | string, payload?: unknown): Promise<TResponse> {
    const eventPayload =
      typeof request === "string"
        ? this.getRequest<TResponse>(request as string, payload)
        : request;
    return this.mediator.send(eventPayload);
  }
  publish<TEvent extends IEvent>(event: TEvent): Promise<void>;
  publish<TPayload>(eventName: string, payload: TPayload): Promise<void>;
  publish(event: string | IEvent, payload?: unknown): Promise<void> {
    const eventPayload =
      typeof event === "string" ? this.getEvent(event as string, payload) : event;
    const classConstructor =
      eventPayload.constructor || Object.getPrototypeOf(eventPayload).constructor;
    const handlerSubscriptions = mediatorSettings.dispatcher.notifications._mappings.filter(
      (m) => m.notification === classConstructor,
    );
    if (!handlerSubscriptions.length) return Promise.resolve();
    return this.mediator.publish(eventPayload);
  }
  subscribe<TEvent extends IEvent>(
    event: TEvent,
    handler: (message: TEvent) => void,
  ): BrokerDisposableHandler;
  subscribe<TPayload>(
    eventName: string,
    handler: (message: TPayload) => void,
  ): BrokerDisposableHandler;
  subscribe(event: string | IEventClass, handler: messageHandler): BrokerDisposableHandler {
    const eventType = typeof event === "function" ? event : this.getEventType(event as string);
    const wrappedHandler = createDynamicEventHandler(handler);
    notificationHandler(eventType)(wrappedHandler);
    return disposableFactory(wrappedHandler);
  }
  registerRequest<TRequest extends IRequest<TResponse>, TResponse>(
    request: TRequest,
    handler: (message: TRequest) => TResponse,
  ): BrokerDisposableHandler;
  registerRequest<TPayload, TResponse>(
    requestName: string,
    handler: (message: TPayload) => TResponse,
  ): BrokerDisposableHandler;
  registerRequest(
    request: string | IRequestClass<unknown>,
    handler: messageHandler,
  ): BrokerDisposableHandler {
    const requestType =
      typeof request === "function" ? request : this.getRequestType(request as string);
    const wrappedHandler = createDynamicRequestHandler(handler);
    requestHandler(requestType)(wrappedHandler);
    return disposableFactory(wrappedHandler);
  }

  private getEvent(eventName: string, payload: unknown): IEvent {
    const eventConstructor = this.getEventType(eventName);
    return new eventConstructor(payload) as IEvent;
  }

  private getRequest<TResponse>(requestName: string, payload: unknown): IRequest<TResponse> {
    const requestConstructor = this.getRequestType(requestName);
    return new requestConstructor(payload) as IRequest<unknown>;
  }

  private getEventType(eventName: string): IEventClass {
    if (!this.eventConstructorMap.has(eventName)) {
      const clazz = createDynamicEventClass(eventName);
      this.eventConstructorMap.set(eventName, clazz);
    }
    return this.eventConstructorMap.get(eventName) as IEventClass;
  }

  private getRequestType(requestName: string): IRequestClass<unknown> {
    if (!this.requestConstructorMap.has(requestName)) {
      const clazz = createDynamicRequestClass(requestName);
      this.requestConstructorMap.set(requestName, clazz);
    }
    return this.requestConstructorMap.get(requestName) as IRequestClass<unknown>;
  }
}

const createDynamicEventClass = (eventName: string) => {
  return new Function(`return class Event_${eventName} {
    constructor(payload) {
            this.payload = payload;
        }
    }`)();
};

const createDynamicRequestClass = (requestName: string) => {
  return new Function(`return class Request_${requestName} {
    constructor(payload) {
            Object.assign(this, payload);
        }
    }`)();
};

const usedSuffixes = new Set();

function generateUniqueRandomSuffix() {
  let suffix;
  do {
    suffix = Math.floor(Math.random() * 10000);
  } while (usedSuffixes.has(suffix));

  usedSuffixes.add(suffix);
  return suffix;
}

const createDynamicRequestHandler = (handler: messageHandler) => {
  const className = `RequestHandler_${generateUniqueRandomSuffix()}`;
  return new Function(
    "handler",
    `return class ${className}{
        handle(notification){
            const handlerResult = handler({...notification});
            return handlerResult instanceof Promise ? handlerResult : Promise.resolve(handlerResult);
        }
    }`,
  )(handler);
};

const createDynamicEventHandler = (handler: messageHandler) => {
  const className = `EventHandler_${generateUniqueRandomSuffix()}`;
  return new Function(
    "handler",
    `return class ${className}{
        handle(notification){
            const payload = notification.payload !== undefined ? notification.payload : {...notification};
            const handlerResult = handler(payload);
            return handlerResult instanceof Promise ? handlerResult : Promise.resolve(handlerResult);
        }
    }`,
  )(handler);
};

export const createBroker = (): PrimariaBroker => new Broker(); 