#!/usr/bin/env node
import assert from "node:assert/strict";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

import {
  ACTIVE_DEV_BROWSER_ENDPOINT,
  ACTIVE_DEV_PUBLIC_ENDPOINT,
  REPORT_LIFECYCLE_VERSION,
  deprecatedPublicEndpoints,
  findForbiddenActiveDeprecatedEndpoints,
  historicalReportLifecycle
} from "../internal/dev-report-lifecycle.mjs";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const defaultDeprecatedEndpoint = "http://74.48.78.17:6667";
const statusFields = ["status", "gateStatus", "conclusion"];

function usage() {
  return [
    "Usage:",
    "  node scripts/report-lifecycle.mjs validate <report.json>",
    "  node scripts/report-lifecycle.mjs invalidate <report.json> --reason <text> [--deprecated-endpoint <url>] [--replaced-by <path>] [--out <path>]",
    "",
    "Notes:",
    "  validate checks the active/historical endpoint lifecycle contract for one report.",
    "  invalidate marks one report historical/deprecated without changing evidence payloads.",
    "  --out defaults to in-place; use a historical/ path when keeping an old active filename reserved for regenerated evidence."
  ].join("\n");
}

function parseArgs(argv) {
  if (argv.length === 0 || argv.includes("--help")) {
    return { help: true };
  }
  const [command, reportPath, ...rest] = argv;
  const args = {
    command,
    reportPath,
    deprecatedEndpoint: defaultDeprecatedEndpoint,
    reason: null,
    replacedBy: null,
    out: null
  };

  for (let index = 0; index < rest.length; index += 1) {
    const arg = rest[index];
    if (arg === "--deprecated-endpoint") {
      args.deprecatedEndpoint = rest[++index];
    } else if (arg === "--reason") {
      args.reason = rest[++index];
    } else if (arg === "--replaced-by") {
      args.replacedBy = rest[++index];
    } else if (arg === "--out") {
      args.out = rest[++index];
    } else if (arg === "--help") {
      args.help = true;
    } else {
      throw new Error(`unknown argument: ${arg}`);
    }
  }

  if (args.help || !args.command) return args;
  if (!["validate", "invalidate"].includes(args.command)) {
    throw new Error(`unknown command: ${args.command}`);
  }
  assert.ok(args.reportPath, "report path is required");
  if (args.command === "invalidate") {
    assert.ok(args.reason, "--reason is required for invalidate");
    assert.ok(
      deprecatedPublicEndpoints.includes(args.deprecatedEndpoint),
      `--deprecated-endpoint must be one of ${deprecatedPublicEndpoints.join(", ")}`
    );
  }
  return args;
}

function repoPath(inputPath) {
  const absolutePath = path.resolve(process.cwd(), inputPath);
  assert.ok(
    absolutePath === repoRoot || absolutePath.startsWith(`${repoRoot}${path.sep}`),
    `${inputPath} must stay inside the repository`
  );
  return absolutePath;
}

function relativeRepoPath(inputPath) {
  return path.relative(repoRoot, repoPath(inputPath)).split(path.sep).join("/");
}

function assertObject(value, label) {
  assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`);
}

function assertString(value, label) {
  assert.equal(typeof value, "string", `${label} must be a string`);
  assert.ok(value.trim().length > 0, `${label} must not be empty`);
}

function validateLifecycle(report, raw, label) {
  assertObject(report.reportLifecycle, `${label}.reportLifecycle`);
  const lifecycle = report.reportLifecycle;
  assert.equal(lifecycle.version, REPORT_LIFECYCLE_VERSION, `${label}.reportLifecycle.version`);
  assert.ok(["active", "historical"].includes(lifecycle.state), `${label}.reportLifecycle.state`);
  assert.equal(lifecycle.activeEndpoint, ACTIVE_DEV_PUBLIC_ENDPOINT, `${label}.reportLifecycle.activeEndpoint`);
  assert.equal(lifecycle.activeBrowserEndpoint, ACTIVE_DEV_BROWSER_ENDPOINT, `${label}.reportLifecycle.activeBrowserEndpoint`);
  assertString(lifecycle.summary, `${label}.reportLifecycle.summary`);

  if (lifecycle.state === "active") {
    assert.equal(lifecycle.deprecatedEndpoint, null, `${label}.reportLifecycle.deprecatedEndpoint`);
    assert.deepEqual(
      findForbiddenActiveDeprecatedEndpoints(report, raw),
      [],
      `${label} active report must not present :6666/:6667 as current public endpoint evidence`
    );
    return "active";
  }

  assert.ok(
    deprecatedPublicEndpoints.includes(lifecycle.deprecatedEndpoint),
    `${label}.reportLifecycle.deprecatedEndpoint must name a deprecated public endpoint`
  );
  assert.ok(
    lifecycle.summary.toLowerCase().includes("historical") ||
      lifecycle.summary.toLowerCase().includes("deprecated"),
    `${label}.reportLifecycle.summary must explain the historical/deprecated state`
  );
  return "historical";
}

function primaryStatus(report) {
  return report.status ?? report.gateStatus ?? report.conclusion ?? null;
}

function markNonGreen(report) {
  let hasTopLevelStatus = false;
  for (const field of statusFields) {
    if (Object.hasOwn(report, field)) {
      hasTopLevelStatus = true;
      if (["pass", "ready", "green", "published"].includes(report[field])) {
        report[field] = "blocked";
      }
    }
  }
  if (!hasTopLevelStatus) {
    report.status = "blocked";
  }
}

function appendInvalidationBlocker(report, reason, deprecatedEndpoint) {
  if (!Array.isArray(report.blockers)) {
    report.blockers = [];
  }
  const scope = "deprecated-public-endpoint";
  const exists = report.blockers.some((blocker) => blocker?.scope === scope);
  if (!exists) {
    report.blockers.push({
      type: "environment_blocker",
      scope,
      status: "open",
      summary: `${reason} Deprecated endpoint ${deprecatedEndpoint} cannot be used as active DEV evidence.`
    });
  }
}

async function readReport(reportPath) {
  const absolutePath = repoPath(reportPath);
  const raw = await readFile(absolutePath, "utf8");
  return { absolutePath, raw, report: JSON.parse(raw) };
}

async function validateCommand(args) {
  const relativePath = relativeRepoPath(args.reportPath);
  const { raw, report } = await readReport(args.reportPath);
  const state = validateLifecycle(report, raw, relativePath);
  process.stdout.write(`${JSON.stringify({
    ok: true,
    path: relativePath,
    state,
    primaryStatus: primaryStatus(report)
  })}\n`);
}

async function invalidateCommand(args) {
  const inputRelativePath = relativeRepoPath(args.reportPath);
  const { report } = await readReport(args.reportPath);
  assertObject(report, inputRelativePath);

  report.reportLifecycle = historicalReportLifecycle({
    deprecatedEndpoint: args.deprecatedEndpoint,
    summary: `Historical/deprecated after DEV public endpoint change: ${args.reason}`
  });
  if (args.replacedBy) {
    report.reportLifecycle.replacedBy = relativeRepoPath(args.replacedBy);
  }
  markNonGreen(report);
  appendInvalidationBlocker(report, args.reason, args.deprecatedEndpoint);

  const outPath = args.out ? repoPath(args.out) : repoPath(args.reportPath);
  await mkdir(path.dirname(outPath), { recursive: true });
  const nextRaw = `${JSON.stringify(report, null, 2)}\n`;
  validateLifecycle(report, nextRaw, inputRelativePath);
  await writeFile(outPath, nextRaw);
  process.stdout.write(`${JSON.stringify({
    ok: true,
    input: inputRelativePath,
    output: path.relative(repoRoot, outPath).split(path.sep).join("/"),
    state: "historical",
    deprecatedEndpoint: args.deprecatedEndpoint,
    replacedBy: report.reportLifecycle.replacedBy ?? null
  })}\n`);
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help || !args.command) {
    process.stdout.write(`${usage()}\n`);
    return;
  }
  if (args.command === "validate") {
    await validateCommand(args);
    return;
  }
  if (args.command === "invalidate") {
    await invalidateCommand(args);
  }
}

try {
  await main();
} catch (error) {
  process.stderr.write(`[report-lifecycle] ${error instanceof Error ? error.message : String(error)}\n`);
  process.exitCode = 1;
}
