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

export function parsePlaywrightJsonResults(stdout, cwd) {
  if (!stdout.trim()) {
    return { fileResults: new Map(), errors: [] };
  }

  let parsed;
  try {
    parsed = JSON.parse(stdout);
  } catch (error) {
    return {
      fileResults: new Map(),
      errors: [`Could not parse Playwright JSON output: ${formatError(error)}`],
    };
  }

  const fileResults = new Map();
  visitPlaywrightSuites(parsed.suites || [], null, [], fileResults, cwd);
  return {
    fileResults: sanitizePlaywrightFileResults(fileResults),
    errors: (parsed.errors || []).map(formatPlaywrightReporterError).filter(Boolean),
  };
}

export function visitPlaywrightSuites(suites, inheritedFile, inheritedTitlePath, fileResults, cwd) {
  for (const suite of suites || []) {
    const suiteFile = normalizeReportedFile(extractReporterFile(suite) || inheritedFile, cwd);
    const suiteTitle = normalizeSuiteTitle(suite?.title, suiteFile);
    const suiteTitlePath = suiteTitle
      ? [...inheritedTitlePath, suiteTitle].filter(Boolean)
      : inheritedTitlePath;
    for (const child of suite.suites || []) {
      visitPlaywrightSuites([child], suiteFile, suiteTitlePath, fileResults, cwd);
    }
    for (const spec of suite.specs || []) {
      collectPlaywrightSpec(spec, suiteFile, suiteTitlePath, fileResults, cwd);
    }
  }
}

export function collectPlaywrightSpec(spec, inheritedFile, suiteTitlePath, fileResults, cwd) {
  const file = normalizeReportedFile(extractReporterFile(spec) || inheritedFile, cwd);
  if (!file) return;
  const specTitle = firstLine(spec?.title || "Playwright spec failed") || "Playwright spec failed";
  const specPath = [...suiteTitlePath, specTitle].filter(Boolean);
  const failureKey = specPath.join(" > ");

  const current = fileResults.get(file) || {
    failed: false,
    status: "passed",
    error: null,
    durationMs: 0,
    failureDetails: [],
    passedCount: 0,
    skippedCount: 0,
  };

  for (const test of spec.tests || []) {
    const results = Array.isArray(test.results) ? test.results : [];
    current.durationMs += results.reduce(
      (sum, result) => sum + Number(result?.duration || 0),
      0
    );

    const final = choosePlaywrightFinalResult(results);
    const status = classifyPlaywrightTestOutcome(test, final);

    if (status === "failed") {
      current.failed = true;
      current.status = "failed";
      current.error ||= extractPlaywrightFailure(final, spec, test);
      current.failureDetails.push({
        kind: "playwright-spec",
        key: failureKey,
        title: specTitle,
        suitePath: suiteTitlePath,
        message: extractPlaywrightFailure(final, spec, test),
      });
      continue;
    }

    if (status === "skipped") {
      current.skippedCount += 1;
      continue;
    }

    current.passedCount += 1;
  }

  if (!current.failed) {
    current.status = current.passedCount === 0 && current.skippedCount > 0 ? "skipped" : "passed";
  }
  current.failureDetails = mergeFailureDetails(current.failureDetails);
  fileResults.set(file, current);
}

export function choosePlaywrightFinalResult(results) {
  if (!results || results.length === 0) return null;
  return results[results.length - 1];
}

export function isPlaywrightPassingStatus(status) {
  return !status || ["passed", "skipped", "expected"].includes(status);
}

export function classifyPlaywrightTestOutcome(test, finalResult) {
  if (test?.outcome === "unexpected" || !isPlaywrightPassingStatus(finalResult?.status)) {
    return "failed";
  }
  if (test?.outcome === "skipped" || finalResult?.status === "skipped") {
    return "skipped";
  }
  return "passed";
}

export function extractPlaywrightFailure(finalResult, spec, test) {
  const fromResult =
    finalResult?.error?.message ||
    finalResult?.error?.value ||
    finalResult?.error?.stack;
  if (fromResult) return firstLine(fromResult);

  const fromTest = test?.errors?.[0]?.message;
  if (fromTest) return firstLine(fromTest);

  return firstLine(spec?.title || "Playwright test failed");
}

export function formatPlaywrightReporterError(error) {
  if (!error) return null;
  if (typeof error === "string") return firstLine(error);
  if (typeof error.message === "string") return firstLine(error.message);
  if (typeof error.value === "string") return firstLine(error.value);
  return null;
}

export function extractReporterFile(node) {
  if (!node || typeof node !== "object") return null;
  if (typeof node.file === "string" && node.file.length > 0) return node.file;
  if (node.location && typeof node.location.file === "string" && node.location.file.length > 0) {
    return node.location.file;
  }
  return null;
}

export function normalizeReportedFile(filePath, cwd) {
  if (!filePath) return null;
  const absolute = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
  return normalizePathSeparators(path.relative(cwd, absolute));
}

export function firstLine(value) {
  return String(value).split(/\r?\n/).find((line) => line.trim().length > 0)?.trim() || null;
}

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

function formatError(error) {
  if (error instanceof Error) return error.message;
  return String(error);
}

function sanitizePlaywrightFileResults(fileResults) {
  const sanitized = new Map();
  for (const [file, result] of fileResults.entries()) {
    sanitized.set(file, {
      failed: result.failed,
      status: result.status,
      error: result.error,
      durationMs: result.durationMs,
      failureDetails: mergeFailureDetails(result.failureDetails),
    });
  }
  return sanitized;
}

function normalizeSuiteTitle(title, reportedFile) {
  const normalizedTitle = firstLine(title || "");
  if (!normalizedTitle) return null;
  if (!reportedFile) return normalizedTitle;
  const fileName = reportedFile.split("/").pop();
  if (normalizedTitle === reportedFile || normalizedTitle === fileName) {
    return null;
  }
  return normalizedTitle;
}
