import { createHash } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";
import { validateIntegrationManifestFile } from "./integration-manifest.mjs";

const MAX_DOSSIER_BYTES = 512 * 1024;
const expectedAuthorities = new Set(["source-contract", "application-configuration", "provider-conformance", "security-privacy", "operations", "production-release", "deployment"]);
const independentlyAuthoritative = new Set(["provider-conformance", "security-privacy", "operations", "production-release", "deployment"]);
const forbiddenMaterial = /Bearer\s+[A-Za-z0-9._~-]{12,}|(?:client_secret|access_token|api_key|credentialValue|key_prefix)["'=:\s]+[^}\],\s]{8,}|https?:\/\/[^"\s]*(?:callback|webhook)|mmp_[a-z0-9_-]{12,}/i;

const digest = (value) => createHash("sha256").update(JSON.stringify(value), "utf8").digest("hex");

export async function validateLaunchDossierFile(target, { manifestFile } = {}) {
  const file = path.resolve(target);
  const stat = await fs.lstat(file).catch(() => undefined);
  if (!stat) return { status: "fail", file, failures: ["Launch dossier file does not exist."], dossier: undefined };
  if (stat.isSymbolicLink() || !stat.isFile()) return { status: "fail", file, failures: ["Launch dossier must be a regular, non-symbolic-link file."], dossier: undefined };
  if (stat.size > MAX_DOSSIER_BYTES) return { status: "fail", file, failures: ["Launch dossier exceeds the 512 KiB validation limit."], dossier: undefined };
  let parsed;
  try { parsed = JSON.parse(await fs.readFile(file, "utf8")); }
  catch { return { status: "fail", file, failures: ["Launch dossier is not valid JSON."], dossier: undefined }; }
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schemaVersion !== "mappls.application-launch-dossier.v1" || typeof parsed.dossierId !== "string") return { status: "fail", file, failures: ["Launch dossier schemaVersion and dossierId are not recognized."], dossier: undefined };
  const failures = [];
  const serialized = JSON.stringify(parsed);
  if (forbiddenMaterial.test(serialized)) failures.push("Launch dossier contains credential or private destination material.");
  if (!parsed.integrity || parsed.integrity.algorithm !== "sha256" || parsed.integrity.authenticity !== "self-generated-not-signed" || !/^[a-f0-9]{64}$/.test(parsed.integrity.payloadSha256 ?? "")) failures.push("Launch dossier has no recognized self-generated SHA-256 integrity envelope.");
  else {
    const { integrity: _integrity, ...payload } = parsed;
    if (digest(payload) !== parsed.integrity.payloadSha256) failures.push("Launch dossier integrity does not match its emitted payload.");
  }
  if (parsed.decision?.productionReady !== false) failures.push("A developer launch dossier must never claim production readiness.");
  if (parsed.safety?.providerCredentialsAccepted !== false || parsed.safety?.providerPayloadsAccepted !== false || parsed.safety?.providerNetworkCalls !== 0 || parsed.safety?.writesExposed !== false || parsed.safety?.secretsIncluded !== false || parsed.safety?.privateDestinationsIncluded !== false) failures.push("Launch dossier safety boundary is missing or weakened.");
  const authorities = Array.isArray(parsed.authorities) ? parsed.authorities : [];
  const ids = authorities.map((item) => item?.id);
  if (authorities.length !== expectedAuthorities.size || new Set(ids).size !== expectedAuthorities.size || ids.some((id) => !expectedAuthorities.has(id))) failures.push("Launch dossier authority ledger is incomplete or contains an unknown authority.");
  if (authorities.some((item) => item?.required !== true || !new Set(["satisfied", "blocked", "unproven"]).has(item?.status))) failures.push("Every launch authority must be required and have a recognized status.");
  if (authorities.some((item) => independentlyAuthoritative.has(item.id) && item.status !== "unproven")) failures.push("Developer evidence cannot satisfy independent provider, security, operations, release, or deployment authority.");
  const satisfied = authorities.filter((item) => item.status === "satisfied").length;
  const blocked = authorities.filter((item) => item.status === "blocked").length;
  const unproven = authorities.filter((item) => item.status === "unproven").length;
  if (parsed.decision?.satisfiedAuthorities !== undefined && parsed.decision.satisfiedAuthorities !== satisfied) failures.push("Launch dossier satisfied-authority count does not match its ledger.");
  if (parsed.decision?.requiredAuthorities !== undefined && parsed.decision.requiredAuthorities !== authorities.length) failures.push("Launch dossier required-authority count does not match its ledger.");
  if (parsed.decision?.blockedAuthorities !== undefined && parsed.decision.blockedAuthorities !== blocked) failures.push("Launch dossier blocked-authority count does not match its ledger.");
  if (parsed.decision?.unprovenAuthorities !== undefined && parsed.decision.unprovenAuthorities !== unproven) failures.push("Launch dossier unproven-authority count does not match its ledger.");
  if (parsed.decision?.applicationConfigured !== undefined && parsed.decision.applicationConfigured !== authorities.some((item) => item.id === "application-configuration" && item.status === "satisfied")) failures.push("Launch dossier applicationConfigured decision does not match application authority.");
  if (parsed.decision?.status !== undefined && parsed.decision.status !== (blocked ? "blocked-before-independent-validation" : "ready-for-independent-validation")) failures.push("Launch dossier decision status does not match blocked authority evidence.");
  if (!parsed.application || typeof parsed.application.id !== "string" || !parsed.application.id || typeof parsed.application.environment !== "string") failures.push("Launch dossier has no bounded application identity and environment.");
  if (!parsed.evidence?.integrationManifest || !/^[a-f0-9]{64}$/.test(parsed.evidence.integrationManifest.sha256 ?? "") || !Array.isArray(parsed.evidence.integrationManifest.sourceFingerprints)) failures.push("Launch dossier has no exact integration-manifest digest and source-fingerprint ledger.");
  let manifestCrossChecked = false;
  if (manifestFile) {
    const manifestReport = await validateIntegrationManifestFile(manifestFile);
    if (manifestReport.status !== "pass") failures.push(`Linked integration manifest failed validation: ${manifestReport.failures.join(" ")}`);
    else {
      const manifestDocument = JSON.parse(await fs.readFile(path.resolve(manifestFile), "utf8"));
      if (manifestDocument.manifestId !== parsed.evidence?.integrationManifest?.manifestId) failures.push("Linked integration manifest identity does not match the launch dossier.");
      if (digest(manifestDocument) !== parsed.evidence?.integrationManifest?.sha256) failures.push("Linked integration manifest digest does not match the launch dossier.");
      manifestCrossChecked = failures.length === 0;
    }
  }
  return {
    status: failures.length ? "fail" : "pass",
    file,
    failures,
    dossier: {
      id: parsed.dossierId,
      applicationId: parsed.application?.id,
      decision: parsed.decision?.status,
      productionReady: parsed.decision?.productionReady,
      satisfiedAuthorities: authorities.filter((item) => item.status === "satisfied").length,
      requiredAuthorities: authorities.length,
      manifestId: parsed.evidence?.integrationManifest?.manifestId,
      payloadSha256: parsed.integrity?.payloadSha256,
      manifestCrossChecked,
    },
  };
}
