const assert = require('assert');
const { generateBase1Step } = require('../src/lib/base1_algorithm');
const {
  EXPRESSION_TREE_VERIFIER_VERSION,
  verifyCandidateStep,
  verifyOfficialStep,
  verifierInfo,
} = require('../src/lib/expression_tree_verifier');

function short(hash) {
  return hash ? `${hash.slice(0, 10)}...${hash.slice(-6)}` : '';
}

function deepClone(value) {
  return JSON.parse(JSON.stringify(value));
}

function makeWrongCandidate(step) {
  const candidate = deepClone({
    algorithm: step.algorithm,
    trees: step.trees,
    operator_units: step.operator_units,
  });
  candidate.trees.u = {
    type: 'op',
    unit: {
      id: 'forced_wrong_offset',
      operator_id: 'add',
      slot: 'offset',
      static_param: {
        id: 'forced_wrong_offset_param',
        value: 1,
        range: [1, 1],
        non_zero: true,
      },
      dynamic_param: null,
    },
    children: [candidate.trees.u, { type: 'const', value: 1 }],
  };
  return candidate;
}

function makeOverBudgetCandidate(step) {
  const candidate = deepClone({
    algorithm: step.algorithm,
    trees: step.trees,
    operator_units: step.operator_units,
  });
  const wrap = (node, depth) => {
    if (depth <= 0) return node;
    return {
      type: 'op',
      unit: {
        id: `over_budget_${depth}`,
        operator_id: 'add',
        slot: 'offset',
        static_param: {
          id: `over_budget_param_${depth}`,
          value: 1,
          range: [1, 1],
          non_zero: true,
        },
        dynamic_param: null,
      },
      children: [wrap(node, depth - 1), { type: 'const', value: 1 }],
    };
  };
  candidate.trees.u = wrap(candidate.trees.u, 300);
  return candidate;
}

function resultRow(label, result) {
  return {
    case: label,
    ok: result.ok,
    stage: result.stage,
    code: result.code,
    points: result.point_set?.total || '',
    mode: result.point_set?.mode || '',
    point_hash: short(result.point_set?.hash),
    context_hash: short(result.context_set?.hash),
    behavior_hash: short(result.behavior_hash),
    detail: result.failure?.code || result.failure?.message || result.message,
  };
}

function runCase(layerNo, stepNo, previousHash, options = {}) {
  const step = generateBase1Step({ layerNo, stepNo, previousHash });
  const info = verifierInfo(step, options);
  const exact = verifyOfficialStep(step, options);
  const wrong = verifyCandidateStep({
    officialStep: step,
    candidateStep: makeWrongCandidate(step),
    options,
  });
  const overBudget = verifyCandidateStep({
    officialStep: step,
    candidateStep: makeOverBudgetCandidate(step),
    options,
  });

  assert.strictEqual(exact.ok, true);
  assert.strictEqual(wrong.ok, false);
  assert.strictEqual(wrong.code, 'BEHAVIOR_MISMATCH');
  assert.strictEqual(overBudget.ok, false);
  assert.strictEqual(overBudget.code, 'ANSWER_BUDGET_REJECTED');

  console.log(`\nLayer ${layerNo} Step ${stepNo}`);
  console.log(
    JSON.stringify(
      {
        verifier: EXPRESSION_TREE_VERIFIER_VERSION,
        formula_hash: short(step.formula_hash),
        grid: `${step.grid_size}x${step.grid_size}`,
        score: step.scores.final_score,
        verifier_point_mode: info.point_set.mode,
        verifier_points: info.point_set.total,
        point_set_hash: short(info.point_set.hash),
        context_set_hash: short(info.context_set.hash),
      },
      null,
      2,
    ),
  );
  console.table([resultRow('exact_candidate', exact), resultRow('mutated_candidate', wrong), resultRow('over_budget_candidate', overBudget)]);
}

function main() {
  runCase(10, 1, 'verifier-sample-low');
  runCase(80, 18, 'verifier-sample-mid');
  runCase(5999, 40, 'verifier-sample-high', { mode: 'sample', minimum_sample_points: 220 });
  console.log('\nExpression-tree verifier sample passed.');
}

main();
