import { promises as fs } from "node:fs";
import path from "node:path";
import { integrationManifests, resolveIntegrationManifest } from "./catalog.mjs";

const MAX_MANIFEST_BYTES = 256 * 1024;
const forbiddenEvidence = /Bearer\s+[A-Za-z0-9._~-]{12,}|(?:client_secret|access_token|api_key|credentialValue|key_prefix)["'=:\s]+[^}\],\s]{8,}|https?:\/\/[^"\s]*callback/i;

export async function writeIntegrationManifest(manifest, target) {
  const destination = path.resolve(target);
  const existing = await fs.lstat(destination).catch(() => undefined);
  if (existing?.isSymbolicLink()) throw new Error("manifest_target_is_symbolic_link");
  if (existing) throw new Error("manifest_target_exists");
  await fs.mkdir(path.dirname(destination), { recursive: true });
  await fs.writeFile(destination, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
  return destination;
}

function validateApplicationEvidence(evidence) {
  if (!evidence || typeof evidence !== "object" || Array.isArray(evidence)) return { ok: false, reason: "applicationEvidence must be an object" };
  const serialized = JSON.stringify(evidence);
  if (serialized.length > 64 * 1024) return { ok: false, reason: "applicationEvidence exceeds 64 KiB" };
  if (forbiddenEvidence.test(serialized)) return { ok: false, reason: "applicationEvidence contains credential or private destination material" };
  if (typeof evidence.application?.id !== "string" || !evidence.application.id || typeof evidence.application?.environment !== "string") return { ok: false, reason: "applicationEvidence requires a bounded application identity and environment" };
  if (!Array.isArray(evidence.productAccess) || !evidence.readiness || typeof evidence.readiness.liveReady !== "boolean") return { ok: false, reason: "applicationEvidence requires product access and readiness status" };
  return { ok: true };
}

export async function validateIntegrationManifestFile(target) {
  const file = path.resolve(target);
  const stat = await fs.lstat(file).catch(() => undefined);
  if (!stat) return { status: "fail", file, failures: ["Manifest file does not exist."], manifest: undefined, applicationBound: false };
  if (stat.isSymbolicLink() || !stat.isFile()) return { status: "fail", file, failures: ["Manifest must be a regular, non-symbolic-link file."], manifest: undefined, applicationBound: false };
  if (stat.size > MAX_MANIFEST_BYTES) return { status: "fail", file, failures: ["Manifest exceeds the 256 KiB validation limit."], manifest: undefined, applicationBound: false };
  let parsed;
  try { parsed = JSON.parse(await fs.readFile(file, "utf8")); } catch { return { status: "fail", file, failures: ["Manifest is not valid JSON."], manifest: undefined, applicationBound: false }; }
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schemaVersion !== "mappls.integration-manifest.v1" || typeof parsed.manifestId !== "string") return { status: "fail", file, failures: ["Manifest schemaVersion and manifestId are not recognized."], manifest: undefined, applicationBound: false };
  const canonical = integrationManifests.find((item) => item.manifestId === parsed.manifestId);
  if (!canonical) return { status: "fail", file, failures: ["Manifest identity is not present in the bundled source catalog."], manifest: parsed, applicationBound: false };
  const comparable = { ...parsed };
  const applicationEvidence = comparable.applicationEvidence;
  delete comparable.applicationEvidence;
  const failures = [];
  if (JSON.stringify(comparable) !== JSON.stringify(canonical)) failures.push("Canonical fields differ from the bundled catalog; regenerate the manifest and review the source change.");
  if (applicationEvidence) {
    const evidence = validateApplicationEvidence(applicationEvidence);
    if (!evidence.ok) failures.push(evidence.reason);
  }
  return {
    status: failures.length ? "fail" : "pass",
    file,
    failures,
    manifest: { id: parsed.manifestId, useCase: parsed.selection?.useCaseSlug, platform: parsed.selection?.primaryPlatform, mode: parsed.selection?.operatingMode, contracts: parsed.contracts?.length ?? 0, sourceFingerprints: parsed.sourceEvidence?.fingerprints?.length ?? 0 },
    applicationBound: Boolean(applicationEvidence),
  };
}

export function selectIntegrationManifest(input) {
  const manifest = resolveIntegrationManifest(input);
  if (!manifest) throw new Error("unknown_manifest_selection");
  return manifest;
}
