import { formatError } from "./formatting.mjs";
import { runDalTask, runHttpK6Task } from "./default-runtime-runner.mjs";
import { runPlaywrightTask } from "./playwright-runner.mjs";

const HTTP_K6_TYPES = new Set(["integration", "e2e", "scenario", "load"]);

export function createWorker(workerId, productDir) {
  return {
    workerId,
    productDir,
    currentGraphKey: null,
    graphSwitches: 0,
    taskCount: 0,
  };
}

export async function runWorker(
  worker,
  queue,
  runtimeManager,
  trackers,
  timingUpdates,
  lifecycle,
  claimNextTask,
  recordTaskOutcome,
  recordGraphError,
  reporter = null
) {
  const startedAt = Date.now();
  const errors = [];

  try {
    while (true) {
      if (lifecycle.isStopRequested()) break;
      const task = claimNextTask(queue, worker.currentGraphKey, (candidate) =>
        runtimeManager.canAcquire(candidate)
      );
      if (!task) {
        const blockedTasks = runtimeManager.drainFailedTasks(queue);
        if (blockedTasks.length > 0) {
          const now = Date.now();
          const recordedGraphs = new Set();
          for (const blocked of blockedTasks) {
            recordTaskOutcome(trackers, blocked.task, {
              failed: false,
              status: "not_run",
              reason: blocked.reason,
              durationMs: 0,
              startedAt: now,
              finishedAt: now,
              error: null,
            });
            if (!recordedGraphs.has(blocked.graph.key)) {
              recordGraphError(trackers, blocked.graph, blocked.message, now);
              recordedGraphs.add(blocked.graph.key);
            }
          }
          continue;
        }
        if (queue.length === 0) break;
        await new Promise((resolve) => setTimeout(resolve, 10));
        continue;
      }

      let lease = null;
      try {
        if (worker.currentGraphKey && worker.currentGraphKey !== task.graphKey) {
          worker.graphSwitches += 1;
        }
        worker.currentGraphKey = task.graphKey;
        lease = await runtimeManager.acquire(task);
        const outcome = await runTask(lease.context, task, lifecycle, lease, reporter);
        recordTaskOutcome(trackers, outcome.task, outcome);
        reporter?.taskFinished?.(outcome.task, outcome);
        timingUpdates.push({
          key: outcome.task.timingKey,
          durationMs: outcome.durationMs,
        });
        worker.taskCount += 1;
        await runtimeManager.release(lease);
      } catch (error) {
        const message = formatError(error);
        errors.push(message);
        recordGraphError(trackers, { targetNames: lease?.context?.targetNames || [task.targetName] }, message);
        await runtimeManager.release(lease, {
          invalidate: lease !== null,
        });
      }
    }
  } finally {
    worker.currentGraphKey = null;
  }

  return {
    workerId: worker.workerId,
    failed: errors.length > 0,
    durationMs: Date.now() - startedAt,
    taskCount: worker.taskCount,
    graphSwitches: worker.graphSwitches,
    errors,
  };
}

async function runTask(context, task, lifecycle, lease, reporter = null) {
  const targetConfig = context.configByName.get(task.targetName);
  if (!targetConfig) {
    throw new Error(`Runtime instance is missing target config "${task.targetName}"`);
  }

  if (task.framework === "playwright") {
    return runPlaywrightTask(targetConfig, task, lifecycle, lease, reporter);
  }
  if (task.type === "dal") {
    return runDalTask(targetConfig, task, lifecycle, lease, reporter);
  }
  if (task.framework === "k6" && HTTP_K6_TYPES.has(task.type)) {
    return runHttpK6Task(targetConfig, task, lifecycle, lease, reporter);
  }

  throw new Error(
    `Unsupported task combination for ${task.targetName}: type=${task.type} framework=${task.framework}`
  );
}
