import { createHash } from "node:crypto";
import { deriveRunrailReviewDecision } from "./runrail-review-policy-core.mjs";
import {
  deriveRunrailCandidateAuditMetadata,
  RUNRAIL_DEEPSEEK_CANDIDATE_LABELS,
  runRunrailDeepSeekCandidateAdvisory,
  verifyRunrailCandidateAudit
} from "./runrail-deepseek-advisory-core.mjs";

export const RUNRAIL_CANDIDATE_ADVISORY = Object.freeze({
  schemaVersion: 2,
  artifactType: "runrail-candidate-advisory",
  provider: "runrail-local",
  adapter: "deterministic-policy-v1",
  model: "runrail-review-policy-v1",
  supportedProviders: Object.freeze(["deterministic", "deepseek"])
});

export const RUNRAIL_CANDIDATE_LABELS = RUNRAIL_DEEPSEEK_CANDIDATE_LABELS;

const candidateOrder = Object.freeze(Object.keys(RUNRAIL_CANDIDATE_LABELS));
const redactionFields = Object.freeze([
  "auditRunIdTransmitted",
  "rawSourceTransmitted",
  "rawDiffTransmitted",
  "filePathsTransmitted",
  "fileNamesTransmitted",
  "commitIdsTransmitted",
  "sourceChecksumTransmitted",
  "diffHashTransmitted",
  "repositoryIdentityTransmitted",
  "taskTextTransmitted",
  "apiKeyPersisted",
  "apiKeyLogged",
  "rawProviderContentPersisted",
  "rawProviderContentLogged"
]);

export class RunrailCandidateAdvisoryError extends Error {
  constructor(code, message) {
    super(message);
    this.name = "RunrailCandidateAdvisoryError";
    this.code = code;
  }
}

function fail(code, message) {
  throw new RunrailCandidateAdvisoryError(code, message);
}

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 deterministicSelection(audit) {
  const decision = deriveRunrailReviewDecision({
    changedFiles: audit.policy.changedFiles,
    diffLineCount: audit.policy.diffLineCount,
    verifierPassed: audit.verifier.passed,
    reviewContract: audit.schemaVersion >= 4 ? audit.reviewContract : null
  });
  const candidateIds = candidateOrder.filter((candidateId) => decision.candidateIds.includes(candidateId));
  if (candidateIds.length < 1 || candidateIds.includes("ABSTAIN")) {
    fail("deterministic-candidate-missing", "The verified Runrail policy did not produce an approved candidate ID.");
  }
  return {
    candidateIds,
    confidence: 1,
    abstained: false
  };
}

export function runRunrailDeterministicCandidateAdvisory({ audit } = {}) {
  verifyRunrailCandidateAudit(audit);
  if (audit.passed !== true || audit.preflightPassed !== true) {
    fail("preflight-not-passed", "Deterministic candidate advisory requires a passing local Runrail preflight.");
  }
  const metadata = deriveRunrailCandidateAuditMetadata(audit);
  const selection = deterministicSelection(audit);
  const requestBody = { schemaVersion: 1, metadata };
  const responseBody = {
    candidateIds: selection.candidateIds,
    confidence: selection.confidence
  };
  return {
    schemaVersion: RUNRAIL_CANDIDATE_ADVISORY.schemaVersion,
    artifactType: RUNRAIL_CANDIDATE_ADVISORY.artifactType,
    advisoryOnly: true,
    provider: RUNRAIL_CANDIDATE_ADVISORY.provider,
    adapter: RUNRAIL_CANDIDATE_ADVISORY.adapter,
    model: RUNRAIL_CANDIDATE_ADVISORY.model,
    networkUsed: false,
    candidateIds: selection.candidateIds,
    candidates: selection.candidateIds.map((id) => ({ id, label: RUNRAIL_CANDIDATE_LABELS[id] })),
    abstained: selection.abstained,
    confidence: selection.confidence,
    confidenceKind: "deterministic-policy-match",
    authority: "none",
    sourceChecksumSha256: audit.checksumSha256,
    requestSha256: sha256(stableJson(requestBody)),
    responseSha256: sha256(stableJson(responseBody)),
    transmittedFields: [],
    redactions: Object.fromEntries(redactionFields.map((field) => [field, false]))
  };
}

export async function runRunrailCandidateAdvisory({
  audit,
  provider = "deterministic",
  allowNetwork = false
} = {}) {
  if (!RUNRAIL_CANDIDATE_ADVISORY.supportedProviders.includes(provider)) {
    fail("unsupported-advisory-provider", "Runrail supports only deterministic or deepseek candidate advisory providers.");
  }
  if (provider === "deterministic") {
    if (allowNetwork) {
      fail("deterministic-network-not-allowed", "The deterministic Runrail advisory does not accept network permission.");
    }
    return runRunrailDeterministicCandidateAdvisory({ audit });
  }
  return runRunrailDeepSeekCandidateAdvisory({ audit, allowNetwork });
}
