const fs = require('fs');
const path = require('path');
const { performance } = require('perf_hooks');

const ROOT = path.resolve(__dirname, '..');
const runId = new Date().toISOString().replace(/[:.]/g, '-');
const defaultDataDir = path.join(ROOT, 'data', `stress-base1-${runId}`);

process.env.MAZEX_DATA_DIR ||= defaultDataDir;
process.env.MAZEX_AUTO_BROADCAST ||= '0';

const {
  appendBase1ConfirmationBlock,
  createBase1StateSnapshot,
  getBase1Account,
  getBase1ChainSummary,
  getBase1FeeSchedule,
  getBase1RewardLedger,
  getCurrentBase1Public,
  loadBase1State,
  makeCandidateFromOfficial,
  replayBase1Chain,
  replayBase1BlocksFromSnapshot,
  resetBase1State,
  sampleBase1Point,
  submitBase1Candidate,
  submitBase1Transfer,
  verifyBase1Chain,
} = require('../src/lib/base1_state');
const { evaluateBase1Step, generateBase1Step, layerSchedule } = require('../src/lib/base1_algorithm');
const { createWallet, payloadHash, signPayload, verifySignature } = require('../src/lib/wallet');

const CONFIG = {
  solves: Math.max(1, Math.trunc(Number(process.env.MAZEX_STRESS_SOLVES || 80))),
  samplesPerStep: Math.max(0, Math.trunc(Number(process.env.MAZEX_STRESS_SAMPLES_PER_STEP || 20))),
  invalidEvery: Math.max(0, Math.trunc(Number(process.env.MAZEX_STRESS_INVALID_EVERY || 5))),
  transfers: Math.max(0, Math.trunc(Number(process.env.MAZEX_STRESS_TRANSFERS || 25))),
  wallets: Math.max(1, Math.trunc(Number(process.env.MAZEX_STRESS_WALLETS || 120))),
  replayEvery: Math.max(1, Math.trunc(Number(process.env.MAZEX_STRESS_REPLAY_EVERY || 10))),
  confirmationsPerSolve: Math.max(0, Math.trunc(Number(process.env.MAZEX_STRESS_CONFIRMATIONS_PER_SOLVE || 2))),
  highLayerVariants: Math.max(1, Math.trunc(Number(process.env.MAZEX_STRESS_HIGH_LAYER_VARIANTS || 8))),
  keepData: ['1', 'true', 'yes'].includes(String(process.env.MAZEX_STRESS_KEEP_DATA || '').toLowerCase()),
};

function assert(condition, message, detail = {}) {
  if (!condition) {
    const error = new Error(message);
    error.detail = detail;
    throw error;
  }
}

function assertInsideWorkspace(target) {
  const resolved = path.resolve(target);
  const workspace = path.resolve(ROOT);
  assert(resolved.startsWith(workspace + path.sep), `Refusing to touch path outside workspace: ${resolved}`);
  return resolved;
}

function ms(start) {
  return Number((performance.now() - start).toFixed(2));
}

function currentOfficialCandidate() {
  const state = loadBase1State();
  const step = state.officialStepsByKey[state.currentStepKey];
  assert(step, 'Current official Step is missing');
  return makeCandidateFromOfficial(step);
}

function headGuard() {
  return getCurrentBase1Public().latest_head_guard;
}

function solveCurrent({ solver, chargeFee = true, submitFee = null, tag = 'solve' } = {}) {
  const result = submitBase1Candidate({
    solver,
    candidateStep: currentOfficialCandidate(),
    expectedHead: headGuard(),
    requireExpectedHead: true,
    chargeFee,
    submitFee,
    signedPayloadHash: `${tag}-${Date.now()}-${Math.random()}`,
  });
  assert(result.correct, `Solve failed: ${result.code || result.message}`, result);
  return result;
}

function appendConfirmations(count, label) {
  for (let index = 0; index < count; index += 1) {
    appendBase1ConfirmationBlock({ producer: `${label}-confirm-${index + 1}` });
  }
}

function stressHighLayerGeneration(metrics) {
  const started = performance.now();
  const layers = [1, 10, 40, 80, 160, 320, 1000, 5000, 10000];
  const seen = new Set();
  let generated = 0;
  let maxScore = 0;
  for (const layerNo of layers) {
    const schedule = layerSchedule(layerNo);
    for (let variant = 0; variant < CONFIG.highLayerVariants; variant += 1) {
      const stepNo = Math.min(schedule.step_count, 1 + ((variant * 7) % schedule.step_count));
      const step = generateBase1Step({
        layerNo,
        stepNo,
        previousHash: `stress-high-layer-${layerNo}-${stepNo}-${variant}`,
      });
      assert(!seen.has(step.formula_hash), 'High-layer generation produced a duplicate formula hash', {
        layerNo,
        stepNo,
        formula_hash: step.formula_hash,
      });
      seen.add(step.formula_hash);
      generated += 1;
      maxScore = Math.max(maxScore, step.scores.final_score);
      const N = step.grid_size;
      for (const [x, y] of [
        [0, 0],
        [Math.floor(N / 2), Math.floor(N / 2)],
        [N - 1, N - 1],
      ]) {
        const output = evaluateBase1Step(step, x, y, {
          click_count: variant + 1,
          last_x: (variant * 3) % N,
          last_y: (variant * 5) % N,
          elapsed_seconds: layerNo + variant * 11,
          chain_state: `stress-chain-${variant}`,
        });
        assert(Number.isInteger(output.u) && output.u >= 0 && output.u < N, 'Generated u is out of grid', {
          layerNo,
          stepNo,
          output,
        });
        assert(Number.isInteger(output.v) && output.v >= 0 && output.v < N, 'Generated v is out of grid', {
          layerNo,
          stepNo,
          output,
        });
      }
    }
  }
  metrics.push({
    module: 'high_layer_generation',
    status: 'pass',
    count: generated,
    max_score: maxScore,
    ms: ms(started),
  });
}

function stressWalletSignatures(metrics) {
  const started = performance.now();
  const hashes = new Set();
  for (let index = 0; index < CONFIG.wallets; index += 1) {
    const wallet = createWallet();
    const payload = {
      type: 'STRESS_WALLET_SIGNATURE',
      index,
      chain_head_hash: `0xstress-${index}`,
      amount: index + 1,
      nonce: index + 1000,
    };
    const signed = signPayload({ privateKey: wallet.private_key, payload });
    const verified = verifySignature({
      publicKey: signed.public_key,
      address: signed.address,
      payload: signed.payload,
      signature: signed.signature,
    });
    assert(verified.ok, 'Wallet signature failed verification', { index, verified });
    assert(signed.address === wallet.address, 'Signed address does not match wallet address', { index });
    assert(signed.payload_hash === payloadHash(payload), 'Signed payload hash mismatch', { index });
    assert(!hashes.has(signed.payload_hash), 'Wallet stress generated duplicate payload hash', {
      index,
      payload_hash: signed.payload_hash,
    });
    hashes.add(signed.payload_hash);
  }
  metrics.push({
    module: 'wallet_signature',
    status: 'pass',
    count: CONFIG.wallets,
    ms: ms(started),
  });
}

function stressLocalChain(metrics) {
  const started = performance.now();
  const solver = 'stress-solver';
  const receiverPrefix = 'stress-receiver';
  const fees = getBase1FeeSchedule();
  let paidSolves = 0;
  let invalidSubmissions = 0;
  let samples = 0;
  let transfers = 0;
  let submitFeesBurned = 0;
  let replayMaxMs = 0;
  const stepCommits = new Set();

  resetBase1State();

  const bootstrap = solveCurrent({ solver, chargeFee: false, tag: 'bootstrap' });
  stepCommits.add(bootstrap.solved.step_commit);
  appendConfirmations(2, 'bootstrap');
  assert(getBase1Account(solver).confirmed_balance > 0, 'Bootstrap reward did not become spendable');

  for (let index = 1; index <= CONFIG.solves; index += 1) {
    const before = getCurrentBase1Public().current;
    assert(!stepCommits.has(before.step_commit), 'Current Step commitment repeated during stress run', {
      index,
      step_commit: before.step_commit,
    });
    for (let sampleIndex = 0; sampleIndex < CONFIG.samplesPerStep; sampleIndex += 1) {
      const x = (index * 17 + sampleIndex * 7) % before.grid_size;
      const y = (index * 19 + sampleIndex * 11) % before.grid_size;
      const sample = sampleBase1Point({ userId: solver, x, y });
      assert(sample.u >= 0 && sample.u < before.grid_size, 'Sample u is out of grid', sample);
      assert(sample.v >= 0 && sample.v < before.grid_size, 'Sample v is out of grid', sample);
      assert(sample.gas_cost === fees.sample_fee, 'Sample fee does not match fee schedule', sample);
      samples += 1;
    }

    if (CONFIG.invalidEvery > 0 && index % CONFIG.invalidEvery === 0) {
      const rejected = submitBase1Candidate({
        solver,
        candidateStep: {
          algorithm: 'invalid-stress-candidate',
          trees: {},
        },
        expectedHead: headGuard(),
        requireExpectedHead: true,
        chargeFee: true,
        submitFee: before.invalid_submit_cost,
        signedPayloadHash: `stress-invalid-${index}`,
      });
      assert(!rejected.correct, 'Invalid candidate unexpectedly passed', rejected);
      assert(rejected.submit_fee === null, 'Invalid candidate should not burn a submit fee', rejected);
      assert(rejected.on_chain === false, 'Invalid candidate should remain off-chain', rejected);
      invalidSubmissions += 1;
    }

    const submitFee = getCurrentBase1Public().current.submit_cost;
    const solved = solveCurrent({
      solver,
      chargeFee: true,
      submitFee,
      tag: `paid-solve-${index}`,
    });
    assert(solved.submit_fee?.amount === submitFee, 'Paid solve did not burn submit fee', solved);
    submitFeesBurned += solved.submit_fee.amount;
    paidSolves += 1;
    stepCommits.add(before.step_commit);

    appendConfirmations(CONFIG.confirmationsPerSolve, `solve-${index}`);

    if (index % CONFIG.replayEvery === 0) {
      const replayStarted = performance.now();
      const replay = replayBase1Chain();
      const replayMs = ms(replayStarted);
      replayMaxMs = Math.max(replayMaxMs, replayMs);
      assert(replay.ok, `Replay failed at solve ${index}: ${replay.code || replay.message}`, replay);
    }
  }

  for (let index = 1; index <= CONFIG.transfers; index += 1) {
    const account = getBase1Account(solver);
    const amount = 1 + (index % 5);
    assert(account.confirmed_balance >= amount + fees.transfer_fee, 'Solver balance too low for transfer stress', {
      balance: account.confirmed_balance,
      amount,
      fee: fees.transfer_fee,
    });
    const transfer = submitBase1Transfer({
      from: solver,
      to: `${receiverPrefix}-${index}`,
      amount,
      fee: fees.transfer_fee,
      nonce: account.nonce + 1,
      expectedHead: headGuard(),
      signedPayloadHash: `stress-transfer-${index}`,
    });
    assert(transfer.ok, `Transfer failed: ${transfer.code || transfer.message}`, transfer);
    transfers += 1;
  }

  const replayStarted = performance.now();
  const replay = replayBase1Chain();
  const finalReplayMs = ms(replayStarted);
  replayMaxMs = Math.max(replayMaxMs, finalReplayMs);
  assert(replay.ok, `Final replay failed: ${replay.code || replay.message}`, replay);
  const snapshotSource = loadBase1State();
  const snapshotAnchorIndex = Math.max(0, Math.floor((snapshotSource.chain.blocks.length - 1) * 0.7));
  const snapshotAnchorBlock = snapshotSource.chain.blocks[snapshotAnchorIndex];
  const snapshot = createBase1StateSnapshot({
    state: snapshotSource,
    height: snapshotAnchorBlock.height,
  });
  const snapshotReplayStarted = performance.now();
  const snapshotReplay = replayBase1BlocksFromSnapshot({
    snapshot,
    blocks: snapshotSource.chain.blocks.slice(snapshotAnchorIndex + 1),
  });
  const snapshotReplayMs = ms(snapshotReplayStarted);
  assert(snapshotReplay.ok, `Snapshot replay failed: ${snapshotReplay.code || snapshotReplay.message}`, snapshotReplay);
  assert(snapshotReplay.chain.head_hash === replay.chain.head_hash, 'Snapshot replay head does not match full replay head', {
    snapshot_head: snapshotReplay.chain.head_hash,
    full_head: replay.chain.head_hash,
  });
  const chain = verifyBase1Chain();
  assert(chain.ok, `Chain verification failed: ${chain.code || chain.message}`, chain);

  const state = loadBase1State();
  const account = getBase1Account(solver);
  const expectedBurned = submitFeesBurned + transfers * fees.transfer_fee;
  assert(state.burnedFees === expectedBurned, 'Burned fee accounting mismatch', {
    actual: state.burnedFees,
    expected: expectedBurned,
    paidSolves,
    invalidSubmissions,
    transfers,
    submitFeesBurned,
  });
  assert(account.nonce === paidSolves + transfers, 'Account nonce mismatch after stress run', {
    actual: account.nonce,
    expected: paidSolves + transfers,
  });
  const rewards = getBase1RewardLedger({ limit: 10000 });
  assert(rewards.length >= CONFIG.solves, 'Reward ledger did not record enough solve rewards', {
    rewards: rewards.length,
    solves: CONFIG.solves,
  });

  metrics.push({
    module: 'local_chain_state_machine',
    status: 'pass',
    paid_solves: paidSolves,
    invalid_submissions: invalidSubmissions,
    samples,
    transfers,
    height: getBase1ChainSummary().height,
    cumulative_work: getBase1ChainSummary().cumulative_work_score,
    burned_fees: state.burnedFees,
    submit_fees_burned: submitFeesBurned,
    solver_confirmed_balance: account.confirmed_balance,
    solver_pending_balance: account.pending_balance,
    replay_max_ms: replayMaxMs,
    final_replay_ms: finalReplayMs,
    snapshot_anchor_height: snapshot.anchor.height,
    snapshot_replayed_blocks: snapshotReplay.checked_blocks,
    snapshot_replay_ms: snapshotReplayMs,
    ms: ms(started),
  });
}

function main() {
  const dataDir = assertInsideWorkspace(process.env.MAZEX_DATA_DIR);
  fs.mkdirSync(dataDir, { recursive: true });
  const metrics = [];
  const started = performance.now();
  let report = null;
  try {
    stressWalletSignatures(metrics);
    stressHighLayerGeneration(metrics);
    stressLocalChain(metrics);
    report = {
      ok: true,
      run_id: runId,
      config: CONFIG,
      data_dir: dataDir,
      total_ms: ms(started),
      memory_mb: Object.fromEntries(
        Object.entries(process.memoryUsage()).map(([key, value]) => [key, Number((value / 1024 / 1024).toFixed(2))]),
      ),
      metrics,
    };
    console.log(JSON.stringify(report, null, 2));
    console.table(metrics);
  } catch (error) {
    report = {
      ok: false,
      run_id: runId,
      config: CONFIG,
      data_dir: dataDir,
      total_ms: ms(started),
      error: {
        message: error.message,
        detail: error.detail || null,
        stack: error.stack,
      },
      metrics,
    };
    console.error(JSON.stringify(report, null, 2));
    process.exitCode = 1;
  } finally {
    if (CONFIG.keepData) {
      fs.writeFileSync(path.join(dataDir, 'stress_result.json'), JSON.stringify(report, null, 2), 'utf8');
    } else if (fs.existsSync(dataDir)) {
      const resolved = assertInsideWorkspace(dataDir);
      fs.rmSync(resolved, { recursive: true, force: true });
    }
  }
}

main();
