import path from "path";
import { mergeFailureDetails } from "./failure-details.mjs";

export function buildServiceTrackers(servicePlans, startedAt) {
  const trackers = new Map();

  for (const plan of servicePlans) {
    if (plan.skipped) {
      trackers.set(plan.config.name, {
        name: plan.config.name,
        dbBackend: plan.config.testkit.database?.selectedBackend || null,
        skipped: true,
        suiteCount: 0,
        suites: [],
        suitesByKey: new Map(),
        errors: [],
        errorSet: new Set(),
        startedAt,
        firstTaskAt: null,
        lastTaskAt: null,
        totalTaskDurationMs: 0,
      });
      continue;
    }

    const suites = plan.suites.map((suite) => ({
      key: `${suite.type}:${suite.name}`,
      name: suite.name,
      type: suite.type,
      displayType: suite.displayType || suite.type,
      framework: suite.framework,
      orderIndex: suite.orderIndex,
      fileCount: suite.totalFileCount ?? suite.files.length,
      completedFileCount: 0,
      failedFiles: [],
      failedFileSet: new Set(),
      fileResultsByPath: new Map([
        ...suite.files.map((file) => {
          const normalizedPath = normalizePathSeparators(file);
          return [
            normalizedPath,
            {
              path: normalizedPath,
              failed: false,
              durationMs: 0,
              error: null,
              reason: null,
              status: "not_run",
              artifacts: [],
              failureDetails: [],
            },
          ];
        }),
        ...(suite.skippedFiles || []).map((file) => [
          file.path,
          {
            path: file.path,
            failed: false,
            durationMs: 0,
            error: null,
            reason: file.reason,
            status: "skipped",
            artifacts: [],
            failureDetails: [],
          },
        ]),
      ]),
      durationMs: 0,
      error: null,
    }));

    trackers.set(plan.config.name, {
      name: plan.config.name,
      dbBackend: plan.config.testkit.database?.selectedBackend || null,
      skipped: false,
      suiteCount: suites.length,
      suites,
      suitesByKey: new Map(suites.map((suite) => [suite.key, suite])),
      errors: [],
      errorSet: new Set(),
      startedAt,
      firstTaskAt: null,
      lastTaskAt: null,
      totalTaskDurationMs: 0,
    });
  }

  return trackers;
}

export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now()) {
  const tracker = trackers.get(task.serviceName);
  if (!tracker || tracker.skipped) return;

  const suite = tracker.suitesByKey.get(task.suiteKey);
  if (!suite) return;

  const outcomeDurationMs = Number(outcome.durationMs ?? 0);
  const outcomeFinishedAt = Number(outcome.finishedAt ?? finishedAt);
  const outcomeStartedAt = Number(
    outcome.startedAt ?? Math.max(0, outcomeFinishedAt - outcomeDurationMs)
  );
  tracker.firstTaskAt =
    tracker.firstTaskAt === null
      ? outcomeStartedAt
      : Math.min(tracker.firstTaskAt, outcomeStartedAt);
  tracker.lastTaskAt =
    tracker.lastTaskAt === null
      ? outcomeFinishedAt
      : Math.max(tracker.lastTaskAt, outcomeFinishedAt);
  tracker.totalTaskDurationMs += outcomeDurationMs;

  suite.durationMs += outcomeDurationMs;
  const normalizedPath = normalizePathSeparators(task.file);
  const existingFileResult = suite.fileResultsByPath.get(normalizedPath);
  const status = normalizeOutcomeStatus(outcome);
  if (status !== "skipped") {
    suite.completedFileCount += 1;
  }
  if (existingFileResult) {
    existingFileResult.failed = status === "failed";
    existingFileResult.durationMs = outcomeDurationMs;
    existingFileResult.error = outcome.error;
    existingFileResult.reason = outcome.reason || null;
    existingFileResult.status = status;
    existingFileResult.artifacts = Array.isArray(outcome.artifacts) ? outcome.artifacts : [];
    existingFileResult.failureDetails = mergeFailureDetails(outcome.failureDetails);
  } else {
    suite.fileResultsByPath.set(normalizedPath, {
      path: normalizedPath,
      failed: status === "failed",
      durationMs: outcomeDurationMs,
      error: outcome.error,
      reason: outcome.reason || null,
      status,
      artifacts: Array.isArray(outcome.artifacts) ? outcome.artifacts : [],
      failureDetails: mergeFailureDetails(outcome.failureDetails),
    });
  }
  if (status === "failed" && !suite.failedFileSet.has(task.file)) {
    suite.failedFileSet.add(task.file);
    suite.failedFiles.push(task.file);
  }
  if (outcome.error && !suite.error) {
    suite.error = outcome.error;
  }
}

export function recordGraphError(trackers, graph, message, now = Date.now()) {
  const targetNames = graph?.targetNames || [];
  for (const targetName of targetNames) {
    const tracker = trackers.get(targetName);
    if (tracker && !tracker.skipped) {
      addTrackerError(tracker, message);
      tracker.lastTaskAt = now;
    }
  }
}

export function addTrackerError(tracker, message) {
  if (tracker.errorSet.has(message)) return;
  tracker.errorSet.add(message);
  tracker.errors.push(message);
}

export function finalizeServiceResult(tracker, startedAt, finishedAt) {
  if (!tracker || tracker.skipped) {
    return {
      name: tracker?.name || "unknown",
      failed: false,
      skipped: true,
      suiteCount: 0,
      completedSuiteCount: 0,
      skippedSuiteCount: 0,
      failedSuiteCount: 0,
      totalFileCount: 0,
      completedFileCount: 0,
      passedFileCount: 0,
      failedFileCount: 0,
      skippedFileCount: 0,
      notRunFileCount: 0,
      durationMs: 0,
      totalTaskDurationMs: 0,
      suites: [],
      errors: [],
    };
  }

  const suites = [...tracker.suites]
    .sort((a, b) => a.orderIndex - b.orderIndex || a.name.localeCompare(b.name))
    .map((suite) => finalizeSuite(suite));

  const completedSuiteCount = suites.filter(
    (suite) => suite.completedFileCount + suite.skippedFileCount === suite.fileCount
  ).length;
  const skippedSuiteCount = suites.filter(
    (suite) => suite.skippedFileCount === suite.fileCount && suite.fileCount > 0
  ).length;
  const failedSuiteCount = suites.filter((suite) => suite.failedFileCount > 0).length;
  const totalFileCount = suites.reduce((sum, suite) => sum + suite.fileCount, 0);
  const completedFileCount = suites.reduce((sum, suite) => sum + suite.completedFileCount, 0);
  const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFileCount, 0);
  const passedFileCount = suites.reduce((sum, suite) => sum + suite.passedFileCount, 0);
  const skippedFileCount = suites.reduce((sum, suite) => sum + suite.skippedFileCount, 0);
  const notRunFileCount = totalFileCount - completedFileCount - skippedFileCount;
  const totalTaskDurationMs =
    tracker.totalTaskDurationMs || suites.reduce((sum, suite) => sum + suite.durationMs, 0);
  const durationMs =
    tracker.firstTaskAt !== null && tracker.lastTaskAt !== null
      ? Math.max(0, tracker.lastTaskAt - tracker.firstTaskAt)
      : Math.max(0, finishedAt - startedAt);

  return {
    name: tracker.name,
    dbBackend: tracker.dbBackend,
    failed:
      failedSuiteCount > 0 ||
      tracker.errors.length > 0 ||
      completedSuiteCount < tracker.suiteCount,
    skipped: false,
    suiteCount: tracker.suiteCount,
    completedSuiteCount,
    skippedSuiteCount,
    failedSuiteCount,
    totalFileCount,
    completedFileCount,
    passedFileCount,
    failedFileCount,
    skippedFileCount,
    notRunFileCount,
    durationMs,
    totalTaskDurationMs,
    suites,
    errors: tracker.errors,
  };
}

export function summarizeDbBackend(results) {
  const values = [...new Set(results.map((result) => result.dbBackend).filter(Boolean))];
  if (values.length === 0) return null;
  if (values.length === 1) return values[0];
  return "mixed";
}

function finalizeSuite(suite) {
  const files = [...suite.fileResultsByPath.values()]
    .sort((a, b) => a.path.localeCompare(b.path))
    .map((file) => ({
      path: file.path,
      failed: file.failed,
      status: file.status,
      durationMs: file.durationMs,
      error: file.error,
      reason: file.reason,
      ...(Array.isArray(file.failureDetails) && file.failureDetails.length > 0
        ? { failureDetails: file.failureDetails }
        : {}),
      ...(Array.isArray(file.artifacts) && file.artifacts.length > 0
        ? { artifacts: file.artifacts }
        : {}),
    }));

  return {
    name: suite.name,
    type: suite.displayType,
    framework: formatFrameworkForArtifact(suite.framework),
    failed: suite.failedFiles.length > 0,
    fileCount: suite.fileCount,
    completedFileCount: suite.completedFileCount,
    passedFileCount: files.filter((file) => file.status === "passed").length,
    failedFileCount: suite.failedFiles.length,
    skippedFileCount: files.filter((file) => file.status === "skipped").length,
    notRunFileCount:
      suite.fileCount -
      suite.completedFileCount -
      files.filter((file) => file.status === "skipped").length,
    failedFiles: suite.failedFiles,
    durationMs: suite.durationMs,
    error: suite.error,
    files,
  };
}

function normalizePathSeparators(filePath) {
  return filePath.split(path.sep).join("/");
}

function formatFrameworkForArtifact(framework) {
  if (framework === "k6") return "default";
  return framework;
}

function normalizeOutcomeStatus(outcome) {
  if (outcome?.status === "skipped") return "skipped";
  return outcome?.failed ? "failed" : "passed";
}
