import { createHash } from "node:crypto";

export const SUBSCRIBER_LOCAL_AUDIT_REQUEST = Object.freeze({
  schemaVersion: 1,
  artifactType: "runrail-local-audit-request",
  requestMode: "subscriber-generated-local-template",
  defaultTaskClass: "reviewed-service-repair"
});

export const SUBSCRIBER_LOCAL_AUDIT_TASK_CLASSES = Object.freeze({
  "reviewed-service-repair": Object.freeze({
    id: "reviewed-service-repair",
    label: "Reviewed service repair",
    expectedEvidenceCategory: "reviewed-service-repair"
  }),
  "reviewed-import-repair": Object.freeze({
    id: "reviewed-import-repair",
    label: "Reviewed import repair",
    expectedEvidenceCategory: "reviewed-import-repair"
  })
});

const PACKET_KEYS = Object.freeze([
  "artifactType",
  "checksumSha256",
  "inputContract",
  "limitations",
  "policy",
  "requestId",
  "requestMode",
  "schemaVersion",
  "taskClass"
]);
const TASK_CLASS_KEYS = Object.freeze(["expectedEvidenceCategory", "id", "label"]);
const POLICY_KEYS = Object.freeze([
  "approvalRequiredBeforeSourceApply",
  "arbitraryCommandAllowed",
  "browserExecutionAllowed",
  "cleanGitRequired",
  "fixedVerifierRequired",
  "networkBridgeAllowed",
  "repoUploadAllowed",
  "reviewedCopyRequired",
  "sourceApplyAllowed",
  "worktreeIsolationRequired"
]);
const INPUT_CONTRACT_KEYS = Object.freeze([
  "repoPathSource",
  "serverReceivesRepoPath",
  "serverReceivesTaskText",
  "serverStoresRequest",
  "taskTextSource"
]);

function stableValue(value) {
  if (Array.isArray(value)) return value.map(stableValue);
  if (value && typeof value === "object") {
    return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
  }
  return value;
}

function stableJson(value) {
  return `${JSON.stringify(stableValue(value), null, 2)}\n`;
}

function sha256(value) {
  return createHash("sha256").update(value).digest("hex");
}

function assertExactKeys(value, expected, label) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new TypeError(`${label} must be an object.`);
  }
  const actual = Object.keys(value).sort();
  const wanted = [...expected].sort();
  if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
    throw new TypeError(`${label} contains missing or unsupported fields.`);
  }
}

function requestBody(taskClassId) {
  const taskClass = SUBSCRIBER_LOCAL_AUDIT_TASK_CLASSES[taskClassId];
  if (!taskClass) throw new TypeError("Unsupported local audit task class.");
  const taskClassCopy = { ...taskClass };
  const policy = {
    reviewedCopyRequired: true,
    cleanGitRequired: true,
    worktreeIsolationRequired: true,
    fixedVerifierRequired: true,
    approvalRequiredBeforeSourceApply: true,
    sourceApplyAllowed: false,
    browserExecutionAllowed: false,
    repoUploadAllowed: false,
    networkBridgeAllowed: false,
    arbitraryCommandAllowed: false
  };
  const inputContract = {
    repoPathSource: "operator-local-only",
    taskTextSource: "operator-local-only",
    serverReceivesRepoPath: false,
    serverReceivesTaskText: false,
    serverStoresRequest: false
  };
  const identity = sha256(stableJson({ taskClass: taskClassCopy, policy, inputContract })).slice(0, 16).toUpperCase();
  return {
    schemaVersion: SUBSCRIBER_LOCAL_AUDIT_REQUEST.schemaVersion,
    artifactType: SUBSCRIBER_LOCAL_AUDIT_REQUEST.artifactType,
    requestId: `RUNRAIL-LOCAL-${identity}`,
    requestMode: SUBSCRIBER_LOCAL_AUDIT_REQUEST.requestMode,
    taskClass: taskClassCopy,
    policy,
    inputContract,
    limitations: [
      "request template only; it grants no execution or apply authority",
      "repository path and task text must be supplied locally by the operator",
      "the repository must be a clean reviewed Git copy inside the operator workspace",
      "the engine may only produce a proposal in an isolated worktree",
      "source apply, upload, network bridging, and arbitrary commands remain unavailable"
    ]
  };
}

export function buildSubscriberLocalAuditRequest(taskClassId = SUBSCRIBER_LOCAL_AUDIT_REQUEST.defaultTaskClass) {
  const body = requestBody(String(taskClassId || "").trim());
  return { ...body, checksumSha256: sha256(stableJson(body)) };
}

export function validateSubscriberLocalAuditRequest(value) {
  assertExactKeys(value, PACKET_KEYS, "Local audit request");
  if (value.schemaVersion !== SUBSCRIBER_LOCAL_AUDIT_REQUEST.schemaVersion) {
    throw new TypeError("Unsupported local audit request schema version.");
  }
  if (value.artifactType !== SUBSCRIBER_LOCAL_AUDIT_REQUEST.artifactType) {
    throw new TypeError("Unsupported local audit request artifact type.");
  }
  if (value.requestMode !== SUBSCRIBER_LOCAL_AUDIT_REQUEST.requestMode) {
    throw new TypeError("Unsupported local audit request mode.");
  }
  assertExactKeys(value.taskClass, TASK_CLASS_KEYS, "Local audit request taskClass");
  assertExactKeys(value.policy, POLICY_KEYS, "Local audit request policy");
  assertExactKeys(value.inputContract, INPUT_CONTRACT_KEYS, "Local audit request inputContract");
  const expected = buildSubscriberLocalAuditRequest(value.taskClass.id);
  if (stableJson(value) !== stableJson(expected)) {
    throw new TypeError("Local audit request content or checksum does not match the fixed contract.");
  }
  return expected;
}

export function lockedSubscriberLocalAuditRequest() {
  return {
    ok: false,
    accessActive: false,
    error: "Subscriber access required",
    requestAvailable: false,
    executionAvailableFromBrowser: false,
    customerRepoUploadEnabled: false
  };
}
