import fs from "fs";
import path from "path";
import { extractHttpSuiteRequests, extractPlaywrightVisitedRoutes } from "./next-static-analysis.mjs";
import {
  apiRouteLookupKey,
  HTTP_METHODS,
  dedupeTargets,
  findMatchingApiRouteEntry,
  findMatchingRouteValue,
  toApiRequestPath,
  toSelectionType,
} from "./shared.mjs";
import {
  inferApiRoutesFromTestFile,
  inferOwnerDirectoryFromTestFile,
  inferPageRouteFromTestFile,
  pathMatchesOwner,
} from "./routing.mjs";

function readTestFileContent(entry, context) {
  if (!context?.serviceRoot) return null;
  const absolutePath = path.join(context.serviceRoot, entry.filePath);
  if (!fs.existsSync(absolutePath)) return null;
  return fs.readFileSync(absolutePath, "utf8");
}

function resolveApiRouteEntry(context, method, requestPath) {
  const exact = context.apiRouteByKey.get(apiRouteLookupKey(method, requestPath));
  if (exact) return exact;
  return findMatchingApiRouteEntry(method, requestPath, context.apiRouteEntries || []);
}

export function inferCoveredNodeIdsForTest(entry, context) {
  const coveredNodeIds = new Set();
  const content = readTestFileContent(entry, context);

  // Capability: Playwright page routes (content-first, path-fallback)
  if (entry.framework === "playwright") {
    const gotoRoutes = content ? extractPlaywrightVisitedRoutes(content, entry.filePath) : [];
    const routes = gotoRoutes.length > 0
      ? gotoRoutes
      : (() => { const r = inferPageRouteFromTestFile(entry.filePath, context.nextAppRoot, context.serviceRoot); return r ? [r] : []; })();
    for (const route of routes) {
      const matchedRoute = context.pageByRoute.get(route)
        ? route
        : findMatchingRouteValue(route, [...context.pageByRoute.keys()]);
      const pageEntry = matchedRoute ? context.pageByRoute.get(matchedRoute) : null;
      if (!pageEntry) continue;
      coveredNodeIds.add(pageEntry.node.id);
      for (const target of extractPlaywrightTargets(entry, context)) {
        const surfaceNode = pageEntry.surfacesByTargetValue?.get(target.value);
        if (surfaceNode) coveredNodeIds.add(surfaceNode.id);
      }
    }
  }

  // Capability: HTTP request extraction (any test type with rawReq/fetch/wrapper calls)
  if (content) {
    const requests = extractHttpSuiteRequests(content, entry.filePath);
    for (const request of requests) {
      if (!request.path.startsWith("/api/")) continue;
      const routeEntry = resolveApiRouteEntry(context, request.method, request.path);
      if (routeEntry) coveredNodeIds.add(routeEntry.node.id);
    }
  }

  // Capability: path-based API route inference (fallback when no content matches)
  if (coveredNodeIds.size === 0) {
    const apiRoutes = inferApiRoutesFromTestFile(entry.filePath, context.nextAppRoot, context.serviceRoot);
    for (const route of apiRoutes) {
      for (const method of HTTP_METHODS) {
        const routeEntry = resolveApiRouteEntry(context, method, toApiRequestPath(route));
        if (routeEntry) coveredNodeIds.add(routeEntry.node.id);
      }
    }
  }

  // Capability: DAL owner directory matching
  for (const nodeId of inferDataCapabilitiesFromTestFile(entry, context)) {
    coveredNodeIds.add(nodeId);
  }

  return [...coveredNodeIds].sort();
}

export function buildEvidenceDetails(coveredNodeIds, graph, entry, context) {
  const routes = new Set();
  const requestPaths = new Set();
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
  for (const nodeId of coveredNodeIds) {
    const node = nodeById.get(nodeId);
    if (!node) continue;
    if (node.route) routes.add(node.route);
    if (node.path) requestPaths.add(node.path);
  }
  return buildEvidenceDetailsFromTargets({
    routes: [...routes].sort(),
    requestPaths: [...requestPaths].sort(),
    targets: extractPlaywrightTargets(entry, context),
  });
}

export function buildEvidenceDetailsFromTargets({ routes = [], requestPaths = [], targets = [] }) {
  const details = {};
  if (routes.length === 1) {
    details.route = routes[0];
  }
  if (requestPaths.length > 0) {
    details.requestPaths = [...requestPaths].sort();
  }
  if (targets.length > 0) {
    details.targets = dedupeTargets(targets);
  }
  return Object.keys(details).length > 0 ? details : undefined;
}

export function extractPlaywrightTargets(entry, context) {
  if (!entry || entry.framework !== "playwright" || !context?.serviceRoot) return [];
  const absolutePath = path.join(context.serviceRoot, entry.filePath);
  if (!fs.existsSync(absolutePath)) return [];
  return extractPlaywrightTargetsFromContent(fs.readFileSync(absolutePath, "utf8"));
}

export function extractPlaywrightTargetsFromContent(content) {
  const targets = [];

  for (const match of content.matchAll(/\bgetByTestId\(\s*["'`]([^"'`]+)["'`]\s*\)/gu)) {
    targets.push({
      kind: "testId",
      value: match[1],
      confidence: "high",
    });
  }

  for (const match of content.matchAll(/\blocator\(\s*["'`]\[data-testid=(?:"|')([^"'`\]]+)(?:"|')\]["'`]\s*\)/gu)) {
    targets.push({
      kind: "testId",
      value: match[1],
      confidence: "medium",
    });
  }

  return dedupeTargets(targets);
}

export function inferDataCapabilitiesFromTestFile(entry, context) {
  if (!entry) return [];
  const ownerDirectory = inferOwnerDirectoryFromTestFile(entry.filePath);
  if (!ownerDirectory) return [];
  return inferDataCapabilitiesFromOwner(ownerDirectory, context.dataCapabilities);
}

export function inferDataCapabilitiesFromOwner(ownerDirectory, dataCapabilities = []) {
  return dataCapabilities
    .filter((node) => node.filePath && pathMatchesOwner(node.filePath, ownerDirectory))
    .map((node) => node.id)
    .sort();
}
