import { Socket, io } from "socket.io-client";
import { WorkerQueue } from "./queues";
import { events, EventTypes, JobLogLevel } from "../globalEvents";

export function emitWorkerGlobalEvent<K extends keyof EventTypes>(
  workerSocket: Socket,
  eventName: K,
  data: Parameters<EventTypes[K]>[0],
): void {
  const payload = [data] as Parameters<EventTypes[K]>;
  events.emit(eventName, ...payload);
  workerSocket.emit("globalEvent", eventName, data);
}

/**
 * Generic job factory that manages worker lifecycle.
 *
 * Handles:
 * 1. Requesting a worker ID from the manager
 * 2. Connecting worker socket to the allocated namespace
 * 3. Running the work function
 * 4. Emitting errors via socket if work fails
 * 5. Returning a Promise that resolves when the work completes
 *
 * @param socket - The Socket.IO client connection to the manager
 * @param queueType - The queue type for this job
 * @param workFn - Async function that performs the actual work. Receives the worker socket for logging/events.
 * @returns Promise that resolves when work completes, or rejects if work fails
 */
export async function createWorkerJob<T>(
  socket: Socket,
  queueType: WorkerQueue,
  workFn: (workerSocket: Socket) => Promise<T>,
): Promise<T> {
  const managerUrl = process.env.MANAGER_SOCKET_URL ?? "http://localhost:8671";

  return new Promise((resolve, reject) => {
    // Request a worker ID and namespace from the manager
    socket.emit(
      "requestId",
      queueType,
      (workerId: string | null, requestError?: string) => {
        if (!workerId) {
          const error = new Error(
            requestError ?? "failed to receive worker ID",
          );
          events.emit("job:error", {
            error,
            context: "worker id reservation",
            workerId: "pending",
            queueType,
          });
          reject(error);
          return;
        }

        // Connect to the worker-specific namespace
        const workerSocket = io(`${managerUrl}/worker-${workerId}`, {
          reconnection: false,
        });

        workerSocket.on("connect", async () => {
          const startedAt = Date.now();

          // Emit synchronously BEFORE workFn so the flow + prefix map exist
          // before any global events fired inside workFn arrive.
          emitWorkerGlobalEvent(workerSocket, "job:started", {
            workerId,
            queueType,
          });

          workerLog(
            workerSocket,
            `[job] started queue=${queueType} workerId=${workerId}`,
            "DEBUG",
          );

          try {
            // Run the work function, passing the worker socket for logging
            const result = await workFn(workerSocket);

            workerLog(
              workerSocket,
              `[job] completed queue=${queueType} workerId=${workerId} durationMs=${Date.now() - startedAt}`,
              "DEBUG",
            );

            // Emit finished event before disconnecting
            emitWorkerGlobalEvent(workerSocket, "job:finished", {
              workerId,
              queueType,
            });

            // Cleanup
            workerSocket.disconnect();
            resolve(result);
          } catch (error) {
            workerLog(
              workerSocket,
              `[job] failed queue=${queueType} workerId=${workerId} durationMs=${Date.now() - startedAt}`,
              "ERROR",
            );

            // Emit error through socket so it's logged on manager
            workerSocket.emit("workerError", {
              error,
              context: "work function execution",
            });

            // Emit error event
            emitWorkerGlobalEvent(workerSocket, "job:error", {
              error,
              context: "worker job",
              workerId,
              queueType,
            });

            // Cleanup
            workerSocket.disconnect();
            reject(error);
          }
        });

        workerSocket.on("connect_error", (err) => {
          emitWorkerGlobalEvent(workerSocket, "job:error", {
            error: err,
            context: "worker socket connection",
            workerId,
            queueType,
          });
          reject(err);
        });
      },
    );
  });
}

/**
 * Helper to emit logs from within a worker job.
 *
 * @param workerSocket - The worker socket connection
 * @param message - Log message to emit
 * @param level - Log level (default: INFO)
 */
export function workerLog(
  workerSocket: Socket,
  message: string,
  level: JobLogLevel = "INFO",
): void {
  workerSocket.emit("log", { message, level });
}
