import fs from "fs";
import path from "path";
import { formatDuration } from "../runner/formatting.mjs";
import { findLogSliceByRequestId, readLogTail } from "../runner/logs.mjs";
import {
  findFailureLocation,
  formatLocation,
  renderCodeFrame,
} from "./presentation/code-frames.mjs";

export function loadLatestRunArtifact(productDir) {
  const artifactPath = path.join(productDir, ".testkit", "results", "latest.json");
  if (!fs.existsSync(artifactPath)) {
    throw new Error(`No run artifact found at ${path.relative(productDir, artifactPath)}`);
  }
  return JSON.parse(fs.readFileSync(artifactPath, "utf8"));
}

export function loadCurrentRunArtifact(productDir) {
  const livePath = path.join(productDir, ".testkit", "results", "live.json");
  if (fs.existsSync(livePath)) {
    return JSON.parse(fs.readFileSync(livePath, "utf8"));
  }
  return loadLatestRunArtifact(productDir);
}

export function resolveFileSubject(runArtifact, selector = null, serviceFilter = null) {
  const files = collectFiles(runArtifact, serviceFilter);
  if (files.length === 0) {
    throw new Error("No file results were found in the latest run artifact.");
  }

  if (!selector) {
    return files.find((entry) => entry.file.status === "failed") || files[0];
  }

  const normalizedSelector = normalizePath(selector);
  const exact = files.filter((entry) => entry.file.path === normalizedSelector);
  if (exact.length === 1) return exact[0];
  if (exact.length > 1) {
    throw new Error(`Multiple files matched "${selector}". Re-run with --service to disambiguate.`);
  }

  const suffixMatches = files.filter((entry) => entry.file.path.endsWith(normalizedSelector));
  if (suffixMatches.length === 1) return suffixMatches[0];
  if (suffixMatches.length > 1) {
    throw new Error(`Multiple files matched "${selector}". Re-run with --service to disambiguate.`);
  }

  throw new Error(`No file matched "${selector}".`);
}

export function collectArtifactEntries(productDir, runArtifact, selector = null, serviceFilter = null) {
  const files = selector
    ? [resolveFileSubject(runArtifact, selector, serviceFilter)]
    : collectFiles(runArtifact, serviceFilter);
  const entries = [];

  for (const entry of files) {
    for (const artifactRef of entry.file.artifacts || []) {
      const absolutePath = path.join(productDir, artifactRef.path);
      const payload = fs.existsSync(absolutePath)
        ? JSON.parse(fs.readFileSync(absolutePath, "utf8"))
        : null;
      entries.push({
        ...entry,
        artifactRef,
        absolutePath,
        payload,
      });
    }
  }

  return entries;
}

export function formatFileDetail(productDir, runArtifact, subject, options = {}) {
  const lines = [];
  const failureDetails = subject.file.failureDetails || [];
  const primaryDetail = rankFailureDetails(failureDetails)[0] || null;
  lines.push(`File: ${subject.file.path}`);
  lines.push(`Service: ${subject.service.name}`);
  lines.push(`Suite: ${subject.suite.type}:${subject.suite.name}`);
  lines.push(`Status: ${subject.file.status}`);
  lines.push(`Duration: ${formatDuration(subject.file.durationMs || 0)}`);
  if (subject.file.error) lines.push(`Error: ${subject.file.error}`);

  if (failureDetails.length > 0) {
    lines.push("");
    lines.push("Failure Details:");
    for (const detail of rankFailureDetails(failureDetails).slice(0, options.failureLimit || 5)) {
      lines.push(`  ${detail.title}`);
      if (detail.message) lines.push(`    ${detail.message}`);
      const requestLine = formatRequestLine(detail);
      if (requestLine) lines.push(`    ${requestLine}`);
      const responseLine = formatResponseLine(detail);
      if (responseLine) lines.push(`    ${responseLine}`);
      const location = findFailureLocation(detail, subject.file.error || "");
      if (location) lines.push(`    at ${formatLocation(location, productDir)}`);
    }
  }

  const codeFrame = renderCodeFrame(findFailureLocation(primaryDetail, subject.file.error || ""), {
    cwd: productDir,
  });
  if (codeFrame.length > 0) {
    lines.push("");
    lines.push("Code Frame:");
    for (const line of codeFrame) lines.push(`  ${line}`);
  }

  if (subject.file.triage) {
    lines.push("");
    lines.push("Triage:");
    const triageLines = formatTriage(subject.file.triage);
    for (const line of triageLines) lines.push(`  ${line}`);
  }

  const setupOperations = getSetupOperationsForService(runArtifact, subject.service.name);
  if (setupOperations.length > 0) {
    lines.push("");
    lines.push("Setup:");
    for (const operation of setupOperations.slice(0, 8)) {
      const duration = operation.durationMs == null ? "" : ` ${formatDuration(operation.durationMs)}`;
      const suffix = operation.summary ? ` ${operation.summary}` : "";
      lines.push(`  ${operation.status} ${operation.stage}${duration}${suffix}`);
      if (operation.logRef?.path) lines.push(`    ${operation.logRef.path}`);
      if (operation.error) lines.push(`    ${operation.error}`);
      if (operation.logRef?.path) {
        const setupLogPath = path.join(productDir, operation.logRef.path);
        for (const line of readLogTail(setupLogPath, 4).slice(-4)) {
          lines.push(`    ${line}`);
        }
      }
    }
  }

  const artifacts = collectArtifactEntries(productDir, runArtifact, subject.file.path, subject.service.name);
  if (artifacts.length > 0) {
    lines.push("");
    lines.push("Artifacts:");
    for (const entry of artifacts) {
      lines.push(`  ${entry.artifactRef.name}${entry.artifactRef.kind ? ` [${entry.artifactRef.kind}]` : ""}`);
      if (entry.artifactRef.summary) lines.push(`    ${entry.artifactRef.summary}`);
      for (const previewLine of formatArtifactPreview(entry.payload, options.previewLength || 6)) {
        lines.push(`    ${previewLine}`);
      }
      lines.push(`    ${entry.artifactRef.path}`);
    }
  }

  const logRefs = getServiceLogRefs(runArtifact, subject.service.name);
  if (logRefs.length > 0) {
    lines.push("");
    lines.push("Backend Logs:");
    for (const logRef of logRefs) {
      lines.push(`  ${logRef.runtimeLabel}`);
      lines.push(`    ${logRef.path}`);
      const logPath = path.join(productDir, logRef.path);
      const requestId = primaryDetail?.request?.requestId || null;
      const tail =
        requestId && findLogSliceByRequestId(logPath, requestId, 2).length > 0
          ? findLogSliceByRequestId(logPath, requestId, 2)
          : readLogTail(logPath, options.logTail || 12);
      for (const line of tail.slice(-Math.max(0, options.logTail || 12))) {
        lines.push(`    ${line}`);
      }
    }
  }

  return lines;
}

export function getServiceLogRefs(runArtifact, serviceName) {
  return (runArtifact.logs?.services || []).filter((entry) => entry.serviceName === serviceName);
}

export function getSetupOperationsForService(runArtifact, serviceName) {
  return (runArtifact.setup?.operations || [])
    .filter((entry) => entry.serviceName === serviceName)
    .sort(
      (left, right) =>
        String(left.startedAt || "").localeCompare(String(right.startedAt || "")) ||
        String(left.stage || "").localeCompare(String(right.stage || ""))
    );
}

export function formatArtifactPreview(payload, maxLines = 6) {
  if (!payload) return ["artifact payload missing"];
  if (payload.kind === "agentic-query") {
    return formatAgenticArtifact(payload, maxLines);
  }
  if (payload.kind === "testkit.scenario") {
    return formatScenarioArtifact(payload, maxLines);
  }
  if (payload.kind === "testkit.http-traces") {
    return formatHttpTraceArtifact(payload, maxLines);
  }
  if (payload.contentType === "text/plain" && typeof payload.data?.text === "string") {
    return payload.data.text
      .split(/\r?\n/)
      .filter((line) => line.trim().length > 0)
      .slice(0, maxLines);
  }
  const preview = JSON.stringify(payload.data, null, 2)
    .split(/\r?\n/)
    .slice(0, maxLines);
  return preview;
}

function formatAgenticArtifact(payload, maxLines) {
  const artifact = payload.data || {};
  const lines = [];
  if (artifact.query?.text) lines.push(`Query: ${artifact.query.text}`);
  if (artifact.ui?.latestAssistantMessage?.content) {
    lines.push(`Answer: ${String(artifact.ui.latestAssistantMessage.content).replace(/\s+/g, " ").trim()}`);
  }
  if (Array.isArray(artifact.ui?.resultTables) && artifact.ui.resultTables.length > 0) {
    const table = artifact.ui.resultTables[0];
    lines.push(`Table: ${table.rowCount} rows · columns ${table.columns.join(", ")}`);
  }
  return lines.slice(0, maxLines);
}

function formatHttpTraceArtifact(payload, maxLines) {
  const traces = Array.isArray(payload.data?.traces) ? payload.data.traces : [];
  const lines = [];
  for (const trace of traces.slice(0, maxLines)) {
    lines.push(
      `${trace.method} ${trace.path} -> ${trace.response?.status ?? "?"}${trace.requestId ? ` [${trace.requestId}]` : ""}`
    );
  }
  return lines;
}

function formatScenarioArtifact(payload, maxLines) {
  const artifact = payload.data || {};
  const lines = [];
  if (artifact.scenarioName) lines.push(`Scenario: ${artifact.scenarioName}`);
  if (artifact.seed) lines.push(`Seed: ${artifact.seed}`);
  const choiceEntries = Object.entries(artifact.choices || {});
  if (choiceEntries.length > 0) {
    lines.push(
      `Choices: ${choiceEntries
        .map(([key, value]) => `${key}=${formatScenarioChoiceValue(value)}`)
        .join(", ")}`
    );
  }
  const failedStep = (artifact.steps || []).find((step) => step.status === "failed");
  if (failedStep) {
    lines.push(`Failed Step: ${failedStep.name}`);
  } else if (Array.isArray(artifact.steps) && artifact.steps.length > 0) {
    lines.push(`Steps: ${artifact.steps.map((step) => step.name).join(" -> ")}`);
  }
  return lines.slice(0, maxLines);
}

function rankFailureDetails(details) {
  return [...(Array.isArray(details) ? details : [])].sort((left, right) => {
    return failureDetailRank(left) - failureDetailRank(right) || String(left?.key || "").localeCompare(String(right?.key || ""));
  });
}

function formatScenarioChoiceValue(value) {
  if (typeof value === "string") return value;
  return JSON.stringify(value);
}

function failureDetailRank(detail) {
  if (detail?.kind === "http-assertion") return 1;
  if (detail?.request && detail?.response) return 2;
  if (detail?.location || detail?.stack) return 3;
  if (detail?.message) return 4;
  return 5;
}

function formatRequestLine(detail) {
  const method = detail?.request?.method;
  const path = detail?.request?.path;
  if (!method || !path) return null;
  const requestId = detail?.request?.requestId;
  return requestId ? `request: ${method} ${path} [${requestId}]` : `request: ${method} ${path}`;
}

function formatResponseLine(detail) {
  if (!detail?.response) return null;
  const parts = [`response: ${detail.response.status ?? "?"}`];
  if (detail.response.contentType) parts.push(detail.response.contentType);
  if (detail.response.bodyPreview) parts.push(detail.response.bodyPreview);
  return parts.join(" ");
}

function formatTriage(triage) {
  const lines = [`status: ${triage.status}`];
  if (triage.classifications?.length) {
    lines.push(`classification: ${triage.classifications.join(", ")}`);
  }
  if (triage.availability?.mode) {
    lines.push(
      `validation: ${triage.availability.mode}${triage.availability.reason ? ` (${triage.availability.reason})` : ""}`
    );
  }
  for (const entry of triage.entries || []) {
    lines.push(`issue: ${entry.issue.repo}#${entry.issue.number}`);
    if (entry.issue.url) lines.push(`url: ${entry.issue.url}`);
    if (entry.github?.state) {
      lines.push(`github state: ${entry.github.state}${entry.github.cached ? " (cache)" : ""}`);
    }
    if (entry.validationStatus) lines.push(`validation status: ${entry.validationStatus}`);
    if (entry.findings?.length) {
      for (const finding of entry.findings.slice(0, 3)) {
        lines.push(`finding: ${finding.message}`);
      }
    }
    break;
  }
  return lines;
}

function collectFiles(runArtifact, serviceFilter = null) {
  const files = [];
  for (const service of runArtifact.services || []) {
    if (serviceFilter && service.name !== serviceFilter) continue;
    for (const suite of service.suites || []) {
      for (const file of suite.files || []) {
        files.push({ service, suite, file });
      }
    }
  }
  return files;
}

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