#!/usr/bin/env node
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import { pathToFileURL } from "node:url";
import {
  defaultArtifactPath,
  finalizeReport,
  pushStep,
  runCliAsync,
  writeJson
} from "./live-direct-utils.mjs";
import {
  closeHttpFixtureServer,
  startHttpFixtureServer,
  withTempHarness
} from "./skill-runtime-probe-utils.mjs";

const HELP_TEXT = [
  "Usage: node scripts/product-video-fixture-live-probe.mjs [options]",
  "",
  "Options:",
  "  --out <path>   Output JSON path",
  "  --quiet        Suppress per-step progress logging",
  "  --help         Show help"
].join("\n");

const PNG_BYTES = Buffer.from(
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jX1QAAAAASUVORK5CYII=",
  "base64"
);
const PRODUCT_VIDEO_PASS_STATUS = "pass";
const PRODUCT_VIDEO_READINESS_GATES = [
  { file: "manifest.json", path: ["readiness", "presentation", "status"], label: "manifest.readiness.presentation.status" },
  { file: "manifest.json", path: ["readiness", "productVideo", "status"], label: "manifest.readiness.productVideo.status" },
  { file: "presentation-readiness.json", path: ["summary", "status"], label: "presentation-readiness.json.summary.status" },
  { file: "presentation-readiness.json", path: ["presentationReadiness", "status"], label: "presentation-readiness.json.presentationReadiness.status" },
  { file: "presentation-readiness.json", path: ["productVideoReadiness", "status"], label: "presentation-readiness.json.productVideoReadiness.status" },
  { file: "product.json", path: ["presentationReadiness", "status"], label: "product.json.presentationReadiness.status" },
  { file: "product.json", path: ["productVideoReadiness", "status"], label: "product.json.productVideoReadiness.status" }
];
const PRODUCT_VIDEO_REQUIRED_BUNDLE_FILES = [
  "manifest.json",
  "presentation-readiness.json",
  "product.json",
  "bundle-manifest.json"
];

function parseArgs(argv) {
  const options = {
    out: defaultArtifactPath("odb-product-video-fixture-live-probe"),
    quiet: false
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === "--help") {
      console.log(HELP_TEXT);
      process.exit(0);
    }
    if (arg === "--quiet") {
      options.quiet = true;
      continue;
    }
    if (arg === "--out") {
      const next = argv[index + 1];
      if (!next || next.startsWith("--")) {
        throw new Error("--out requires a file path.");
      }
      options.out = next;
      index += 1;
      continue;
    }
    if (arg.startsWith("--out=")) {
      options.out = arg.slice("--out=".length);
      continue;
    }
    throw new Error(`Unknown option: ${arg}`);
  }

  return options;
}

function resolveFixtureRequestOrigin(request) {
  const host = typeof request.headers.host === "string" && request.headers.host.length > 0
    ? request.headers.host
    : "127.0.0.1";
  return `http://${host}`;
}

export function createProductFixtureServer() {
  return http.createServer((request, response) => {
    const requestOrigin = resolveFixtureRequestOrigin(request);
    const url = new URL(request.url ?? "/", requestOrigin);
    if (url.pathname.startsWith("/assets/")) {
      response.writeHead(200, {
        "content-type": "image/png",
        "content-length": PNG_BYTES.length
      });
      response.end(PNG_BYTES);
      return;
    }

    if (url.pathname !== "/product/widget-pro") {
      response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
      response.end("not found");
      return;
    }

    response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
    const productDescription = "Type Desk Organizer. Connectivity Wireless charging and magnetic cable routing. Widget Pro keeps your desk clear with integrated charging and storage.";
    response.end(`<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Widget Pro | Acme Labs</title>
    <meta name="description" content="${productDescription}">
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "Product",
        "name": "Widget Pro",
        "brand": {
          "@type": "Brand",
          "name": "Acme Labs"
        },
        "image": [
          "${requestOrigin}/assets/widget-01.png",
          "${requestOrigin}/assets/widget-02.png"
        ],
        "description": "${productDescription}",
        "offers": {
          "@type": "Offer",
          "priceCurrency": "USD",
          "price": "79.99",
          "availability": "https://schema.org/InStock"
        }
      }
    </script>
    <style>
      body { font-family: sans-serif; margin: 0; background: #eef2f7; color: #14213d; }
      main { max-width: 920px; margin: 0 auto; padding: 40px 24px 80px; display: grid; gap: 20px; }
      .hero { background: #fff; border-radius: 24px; padding: 28px; box-shadow: 0 14px 32px rgba(15, 23, 42, 0.08); }
      .hero img { width: 220px; height: 220px; border-radius: 20px; object-fit: cover; }
      .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
      ul { padding-left: 20px; }
      .price { font-size: 32px; font-weight: 700; }
      .brand { color: #52606d; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; }
    </style>
  </head>
  <body>
    <main>
      <section class="hero">
        <p class="brand">Acme Labs</p>
        <h1>Widget Pro</h1>
        <p class="price">$79.99</p>
        <p>Widget Pro is the all-in-one desktop organizer for creators who want fewer cables, cleaner lines, and instant charging access.</p>
        <img src="${requestOrigin}/assets/widget-01.png" alt="Widget Pro front view">
      </section>
      <section class="grid">
        <article>
          <h2>Features</h2>
          <ul>
            <li>Integrated wireless charging pad for phones and earbuds</li>
            <li>Hidden tray for pens, adapters, and SD cards</li>
            <li>Magnetic cable channel keeps desks uncluttered</li>
          </ul>
        </article>
        <article>
          <h2>Why it works</h2>
          <p>Designed for hybrid desks, Widget Pro reduces clutter without taking over the workspace.</p>
          <img src="${requestOrigin}/assets/widget-02.png" alt="Widget Pro lifestyle scene">
        </article>
      </section>
    </main>
  </body>
</html>`);
  });
}

function getReviewBundleRoot() {
  return path.join(process.cwd(), "artifacts", "skill-runtime-audit", "review-bundles", "product-video-fixture");
}

function readJsonObject(filePath) {
  if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
    return { value: null, detail: "missing" };
  }
  try {
    const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
      return { value: null, detail: "invalid_shape" };
    }
    return { value: parsed, detail: null };
  } catch {
    return { value: null, detail: "invalid_json" };
  }
}

function isOutsidePath(relativePath) {
  return relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath);
}

function validateExpectedProductVideoRoot(artifactPath, expectedRoot) {
  if (typeof expectedRoot !== "string" || expectedRoot.trim() === "") {
    return "missing_product_video_expected_output_root";
  }
  if (!fs.existsSync(expectedRoot) || !fs.statSync(expectedRoot).isDirectory()) {
    return "product_video_expected_output_root_missing_on_disk";
  }
  const realArtifactPath = fs.realpathSync(artifactPath);
  const realExpectedRoot = fs.realpathSync(expectedRoot);
  const expectedProductVideoRoot = path.join(realExpectedRoot, "product-video");
  const relativePath = path.relative(expectedProductVideoRoot, realArtifactPath);
  if (isOutsidePath(relativePath) || relativePath.includes(path.sep)) {
    return "product_video_artifact_path_outside_expected_output_root";
  }
  return null;
}

function validateBundleManifest(artifactPath, manifestPath) {
  const parsed = readJsonObject(manifestPath);
  if (parsed.detail) return `product_video_bundle_manifest_${parsed.detail}`;
  if (parsed.value.run_id !== path.basename(artifactPath)) {
    return "product_video_bundle_manifest_run_id_mismatch";
  }
  if (!Array.isArray(parsed.value.files) || parsed.value.files.some((file) => typeof file !== "string")) {
    return "product_video_bundle_manifest_files_invalid";
  }
  for (const requiredFile of PRODUCT_VIDEO_REQUIRED_BUNDLE_FILES) {
    if (!parsed.value.files.includes(requiredFile)) {
      return `product_video_bundle_manifest_missing_required_file:${requiredFile}`;
    }
  }
  return null;
}

export function validateProductVideoArtifactBundle(artifactPath, expectedRoot) {
  if (typeof artifactPath !== "string" || artifactPath.trim() === "") {
    return { artifactPath: null, detail: "missing_product_video_artifact_path" };
  }
  if (!fs.existsSync(artifactPath) || !fs.statSync(artifactPath).isDirectory()) {
    return { artifactPath, detail: "product_video_artifact_path_missing_on_disk" };
  }
  if (path.basename(path.dirname(artifactPath)) !== "product-video") {
    return { artifactPath, detail: "product_video_artifact_namespace_mismatch" };
  }
  const rootDetail = validateExpectedProductVideoRoot(artifactPath, expectedRoot);
  if (rootDetail) {
    return { artifactPath, detail: rootDetail };
  }
  const manifestPath = path.join(artifactPath, "bundle-manifest.json");
  if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile()) {
    return { artifactPath, detail: "product_video_bundle_manifest_missing" };
  }
  const manifestDetail = validateBundleManifest(artifactPath, manifestPath);
  if (manifestDetail) {
    return { artifactPath, detail: manifestDetail };
  }
  return { artifactPath, detail: null };
}

function valueAtPath(record, pathParts) {
  let value = record;
  for (const pathPart of pathParts) {
    if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
    value = value[pathPart];
  }
  return value;
}

export function validateProductVideoReadinessBundle(artifactPath, expectedRoot) {
  const artifactValidation = validateProductVideoArtifactBundle(artifactPath, expectedRoot);
  if (artifactValidation.detail) return { ...artifactValidation, statuses: [] };

  const statuses = [];
  const jsonByFile = new Map();
  for (const gate of PRODUCT_VIDEO_READINESS_GATES) {
    let parsed = jsonByFile.get(gate.file);
    if (!parsed) {
      parsed = readJsonObject(path.join(artifactPath, gate.file));
      jsonByFile.set(gate.file, parsed);
    }
    if (parsed.detail) {
      return {
        artifactPath,
        detail: `product_video_readiness_file_${parsed.detail}:${gate.file}`,
        statuses
      };
    }
    const status = valueAtPath(parsed.value, gate.path);
    statuses.push({ label: gate.label, status: typeof status === "string" ? status : null });
    if (status !== PRODUCT_VIDEO_PASS_STATUS) {
      return {
        artifactPath,
        detail: `product_video_readiness_not_pass:${gate.label}=${typeof status === "string" ? status : "missing"}`,
        statuses
      };
    }
  }

  return { artifactPath, detail: null, statuses };
}

export function validateProductVideoWorkflowReadinessArtifact(workflowStatus, artifactPath, expectedRoot) {
  const artifactValidation = workflowStatus === 0
    ? validateProductVideoArtifactBundle(artifactPath, expectedRoot)
    : { artifactPath, detail: null };
  const readinessValidation = workflowStatus === 0 && artifactValidation.detail === null
    ? validateProductVideoReadinessBundle(artifactPath, expectedRoot)
    : { artifactPath, detail: null, statuses: [] };
  return {
    artifactPath,
    detail: artifactValidation.detail ?? readinessValidation.detail,
    statuses: readinessValidation.statuses
  };
}

export function productVideoWorkflowFailureDetail(workflow, artifactDetail) {
  if (workflow.status !== 0) {
    return workflow.detail ?? `product_video_workflow_failed:${workflow.status}`;
  }
  return artifactDetail;
}

function persistArtifactBundle(sourcePath, runId, bundleName) {
  const targetPath = path.join(getReviewBundleRoot(), runId, bundleName);
  fs.rmSync(targetPath, { recursive: true, force: true });
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
  fs.cpSync(sourcePath, targetPath, { recursive: true });
  return targetPath;
}

async function runProbe(options) {
  const runId = path.basename(options.out, path.extname(options.out));
  const report = {
    startedAt: new Date().toISOString(),
    out: options.out,
    reviewBundleDir: path.join(getReviewBundleRoot(), runId),
    steps: []
  };

  await withTempHarness("odb-product-video-fixture", async ({ env, tempRoot }) => {
    const { server, baseUrl } = await startHttpFixtureServer(createProductFixtureServer);
    try {
      const productUrl = `${baseUrl}/product/widget-pro`;
      const outputDir = path.join(tempRoot, "product-video-output");

      pushStep(report, {
        id: "fixture.server",
        status: "pass",
        detail: null,
        data: { productUrl }
      }, { prefix: "[product-video-fixture]", logProgress: !options.quiet });

      const daemonStatus = await runCliAsync(["status", "--daemon"], {
        env,
        allowFailure: true,
        timeoutMs: 10_000
      });
      pushStep(report, {
        id: "infra.daemon_status",
        status: daemonStatus.status === 0 ? "pass" : "fail",
        detail: daemonStatus.status === 0 ? null : daemonStatus.detail
      }, { prefix: "[product-video-fixture]", logProgress: !options.quiet });
      if (daemonStatus.status !== 0) {
        return;
      }

      const metadataOnlyWorkflow = await runCliAsync([
        "product-video",
        "run",
        "--product-url",
        productUrl,
        "--output-dir",
        outputDir,
        "--include-screenshots=false",
        "--timeout-ms",
        "180000"
      ], {
        env,
        allowFailure: true,
        timeoutMs: 360_000
      });

      const metadataOnlyData = metadataOnlyWorkflow.json?.data ?? {};
      const metadataOnlyArtifactPath = typeof metadataOnlyData.artifact_path === "string" ? metadataOnlyData.artifact_path : null;
      const metadataOnlyArtifactValidation = validateProductVideoWorkflowReadinessArtifact(
        metadataOnlyWorkflow.status,
        metadataOnlyArtifactPath,
        outputDir
      );
      const persistedMetadataOnlyArtifactPath = metadataOnlyWorkflow.status === 0
        && metadataOnlyArtifactValidation.detail === null
        ? persistArtifactBundle(metadataOnlyArtifactPath, runId, "metadata-only")
        : null;
      const metadataOnlyOk = metadataOnlyWorkflow.status === 0
        && metadataOnlyArtifactValidation.detail === null
        && persistedMetadataOnlyArtifactPath
        && Array.isArray(metadataOnlyData.screenshots)
        && metadataOnlyData.screenshots.length === 0
        && typeof metadataOnlyData.manifest?.product?.title === "string"
        && metadataOnlyData.manifest.product.title.includes("Widget Pro")
        && typeof metadataOnlyData.manifest?.product?.brand === "string"
        && metadataOnlyData.manifest.product.brand.includes("Acme Labs");

      pushStep(report, {
        id: "workflow.product_video_run_no_screenshots",
        status: metadataOnlyOk ? "pass" : "fail",
        detail: metadataOnlyOk ? null : productVideoWorkflowFailureDetail(
          metadataOnlyWorkflow,
          metadataOnlyArtifactValidation.detail
        ),
        data: {
          artifactPath: persistedMetadataOnlyArtifactPath,
          sourceArtifactPath: metadataOnlyArtifactPath,
          readiness: metadataOnlyArtifactValidation.statuses,
          images: Array.isArray(metadataOnlyData.images) ? metadataOnlyData.images.length : 0,
          screenshots: Array.isArray(metadataOnlyData.screenshots) ? metadataOnlyData.screenshots.length : 0,
          title: metadataOnlyData.manifest?.product?.title ?? null,
          brand: metadataOnlyData.manifest?.product?.brand ?? null
        }
      }, { prefix: "[product-video-fixture]", logProgress: !options.quiet });
      if (!metadataOnlyOk) {
        return;
      }

      const screenshotOutputDir = path.join(tempRoot, "product-video-output-with-screenshots");
      const workflow = await runCliAsync([
        "product-video",
        "run",
        "--product-url",
        productUrl,
        "--output-dir",
        screenshotOutputDir,
        "--timeout-ms",
        "180000"
      ], {
        env,
        allowFailure: true,
        timeoutMs: 360_000
      });

      const data = workflow.json?.data ?? {};
      const artifactPath = typeof data.artifact_path === "string" ? data.artifact_path : null;
      const artifactValidation = validateProductVideoWorkflowReadinessArtifact(
        workflow.status,
        artifactPath,
        screenshotOutputDir
      );
      const persistedArtifactPath = workflow.status === 0
        && artifactValidation.detail === null
        ? persistArtifactBundle(artifactPath, runId, "with-screenshots")
        : null;
      const manifestPath = persistedArtifactPath ? path.join(persistedArtifactPath, "manifest.json") : null;
      const copyPath = persistedArtifactPath ? path.join(persistedArtifactPath, "copy.md") : null;
      const featuresPath = persistedArtifactPath ? path.join(persistedArtifactPath, "features.md") : null;
      const workflowOk = workflow.status === 0
        && artifactValidation.detail === null
        && persistedArtifactPath
        && manifestPath
        && copyPath
        && featuresPath
        && fs.existsSync(manifestPath)
        && fs.existsSync(copyPath)
        && fs.existsSync(featuresPath)
        && Array.isArray(data.screenshots)
        && data.screenshots.length > 0
        && typeof data.manifest?.product?.title === "string"
        && data.manifest.product.title.includes("Widget Pro")
        && typeof data.manifest?.product?.brand === "string"
        && data.manifest.product.brand.includes("Acme Labs");

      pushStep(report, {
        id: "workflow.product_video_run",
        status: workflowOk ? "pass" : "fail",
        detail: workflowOk ? null : productVideoWorkflowFailureDetail(
          workflow,
          artifactValidation.detail
        ),
        data: {
          artifactPath: persistedArtifactPath,
          sourceArtifactPath: artifactPath,
          readiness: artifactValidation.statuses,
          images: Array.isArray(data.images) ? data.images.length : 0,
          screenshots: Array.isArray(data.screenshots) ? data.screenshots.length : 0,
          title: data.manifest?.product?.title ?? null,
          brand: data.manifest?.product?.brand ?? null
        }
      }, { prefix: "[product-video-fixture]", logProgress: !options.quiet });
    } finally {
      await closeHttpFixtureServer(server);
    }
  });

  finalizeReport(report);
  writeJson(options.out, report);
  return report;
}

async function main() {
  const options = parseArgs(process.argv.slice(2));
  const report = await runProbe(options);
  console.log(JSON.stringify({
    ok: report.ok,
    counts: report.counts,
    summary: {
      status: report.ok ? "pass" : "fail",
      artifactPath: options.out
    }
  }, null, 2));
  if (!report.ok) {
    process.exitCode = 1;
  }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch((error) => {
    console.error(error instanceof Error ? error.message : String(error));
    process.exit(1);
  });
}
