#!/usr/bin/env node

/**
 * Dynamic skill catalog discovery tests for clawsec-suite.
 *
 * Tests cover:
 * - Remote index fetch and normalization
 * - Enrichment with suite-local metadata (non-breaking compatibility)
 * - Fallback behavior when remote index is invalid/unavailable
 *
 * Run: node skills/clawsec-suite/test/skill_catalog_discovery.test.mjs
 */

import http from "node:http";
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { pass, fail, report, exitWithResults } from "./lib/test_harness.mjs";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPT_PATH = path.resolve(__dirname, "..", "scripts", "discover_skill_catalog.mjs");

function runCatalogScript(args, env = {}) {
  return new Promise((resolve) => {
    const proc = spawn("node", [SCRIPT_PATH, ...args], {
      env: { ...process.env, ...env },
      stdio: ["ignore", "pipe", "pipe"],
    });

    let stdout = "";
    let stderr = "";

    proc.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });

    proc.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });

    proc.on("close", (code) => {
      resolve({ code, stdout, stderr });
    });
  });
}

function withServer(handler) {
  return new Promise((resolve, reject) => {
    const server = http.createServer(handler);
    server.listen(0, "127.0.0.1", () => {
      const addr = server.address();
      if (!addr || typeof addr === "string") {
        reject(new Error("Failed to bind test server"));
        return;
      }

      resolve({
        url: `http://127.0.0.1:${addr.port}`,
        close: () =>
          new Promise((done) => {
            server.close(() => done());
          }),
      });
    });

    server.on("error", reject);
  });
}

// -----------------------------------------------------------------------------
// Test: remote index is used when valid
// -----------------------------------------------------------------------------
async function testRemoteCatalogSuccess() {
  const testName = "discover_skill_catalog: uses remote index when valid";
  let fixture = null;

  try {
    fixture = await withServer((req, res) => {
      if (req.url !== "/index.json") {
        res.writeHead(404, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ error: "not found" }));
        return;
      }

      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(
        JSON.stringify({
          version: "1.0.0",
          updated: "2026-02-16T08:20:00Z",
          skills: [
            {
              id: "soul-guardian",
              name: "soul-guardian",
              version: "9.9.9",
              description: "Remote skill metadata",
              emoji: "👻",
              category: "security",
              tag: "soul-guardian-v9.9.9",
            },
            {
              id: "clawtributor",
              name: "clawtributor",
              version: "1.2.3",
              description: "Remote clawtributor metadata",
              emoji: "🤝",
              category: "security",
              tag: "clawtributor-v1.2.3",
            },
          ],
        }),
      );
    });

    const result = await runCatalogScript(["--json"], {
      CLAWSEC_SKILLS_INDEX_URL: `${fixture.url}/index.json`,
      CLAWSEC_SKILLS_INDEX_TIMEOUT_MS: "2000",
    });

    if (result.code !== 0) {
      fail(testName, `Expected exit 0, got ${result.code}: ${result.stderr}`);
      return;
    }

    const payload = JSON.parse(result.stdout);
    const clawtributor = payload.skills.find((entry) => entry.id === "clawtributor");
    const soulGuardian = payload.skills.find((entry) => entry.id === "soul-guardian");

    if (
      payload.source === "remote" &&
      payload.updated === "2026-02-16T08:20:00Z" &&
      soulGuardian?.version === "9.9.9" &&
      clawtributor?.requires_explicit_consent === true
    ) {
      pass(testName);
    } else {
      fail(testName, `Unexpected payload: ${result.stdout}`);
    }
  } catch (error) {
    fail(testName, error);
  } finally {
    if (fixture) {
      await fixture.close();
    }
  }
}

// -----------------------------------------------------------------------------
// Test: invalid remote payload falls back to suite-local catalog
// -----------------------------------------------------------------------------
async function testInvalidRemotePayloadFallsBack() {
  const testName = "discover_skill_catalog: invalid remote payload falls back";
  let fixture = null;

  try {
    fixture = await withServer((_req, res) => {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ version: "1.0.0", note: "missing skills" }));
    });

    const result = await runCatalogScript(["--json"], {
      CLAWSEC_SKILLS_INDEX_URL: `${fixture.url}/index.json`,
      CLAWSEC_SKILLS_INDEX_TIMEOUT_MS: "2000",
    });

    if (result.code !== 0) {
      fail(testName, `Expected exit 0, got ${result.code}: ${result.stderr}`);
      return;
    }

    const payload = JSON.parse(result.stdout);
    const hasSoulGuardian = Array.isArray(payload.skills)
      ? payload.skills.some((entry) => entry.id === "soul-guardian")
      : false;

    if (payload.source === "fallback" && hasSoulGuardian && String(payload.warning).includes("skills array")) {
      pass(testName);
    } else {
      fail(testName, `Unexpected payload: ${result.stdout}`);
    }
  } catch (error) {
    fail(testName, error);
  } finally {
    if (fixture) {
      await fixture.close();
    }
  }
}

// -----------------------------------------------------------------------------
// Test: unreachable remote index falls back to suite-local catalog
// -----------------------------------------------------------------------------
async function testUnreachableRemoteFallsBack() {
  const testName = "discover_skill_catalog: unreachable remote index falls back";

  try {
    const result = await runCatalogScript(["--json"], {
      CLAWSEC_SKILLS_INDEX_URL: "http://127.0.0.1:9/index.json",
      CLAWSEC_SKILLS_INDEX_TIMEOUT_MS: "250",
    });

    if (result.code !== 0) {
      fail(testName, `Expected exit 0, got ${result.code}: ${result.stderr}`);
      return;
    }

    const payload = JSON.parse(result.stdout);
    if (payload.source === "fallback" && Array.isArray(payload.skills) && payload.skills.length > 0) {
      pass(testName);
    } else {
      fail(testName, `Unexpected payload: ${result.stdout}`);
    }
  } catch (error) {
    fail(testName, error);
  }
}

// -----------------------------------------------------------------------------
// Main test runner
// -----------------------------------------------------------------------------
async function runTests() {
  console.log("=== ClawSec Skill Catalog Discovery Tests ===\n");

  await testRemoteCatalogSuccess();
  await testInvalidRemotePayloadFallsBack();
  await testUnreachableRemoteFallsBack();

  report();
  exitWithResults();
}

runTests().catch((error) => {
  console.error("Test runner failed:", error);
  process.exit(1);
});
