require('../config/loadEnv')();

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ChainBlock = require('../models/ChainBlock');
const persistentStore = require('../services/persistentStore');

const TARGET_STATUSES = new Set(['PENDING_VALIDATION', 'SEALED_MOCK']);
const dataDir = path.join(__dirname, '..', 'data');
const reportPath = path.join(dataDir, 'finality-backlog-audit-latest.json');

function sha256(value) {
  return crypto.createHash('sha256').update(String(value)).digest('hex');
}

function countBy(rows, keySelector) {
  return rows.reduce((counts, row) => {
    const key = String(keySelector(row) || 'UNKNOWN');
    counts[key] = (counts[key] || 0) + 1;
    return counts;
  }, {});
}

function writeJsonAtomic(filePath, value) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
  fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' });
  fs.renameSync(tempPath, filePath);
}

function buildAudit(state) {
  const blocks = Array.isArray(state.blocks) ? state.blocks : [];
  const votes = Array.isArray(state.validatorVotes) ? state.validatorVotes : [];
  const knownReferences = {
    proof: new Set((state.proofs || []).map((row) => String(row.id || ''))),
    transaction: new Set((state.transactions || []).map((row) => String(row.id || ''))),
    reward: new Set((state.rewardEntries || []).map((row) => String(row.id || '')))
  };
  const votesByBlock = new Map();
  for (const vote of votes) {
    const blockId = String(vote.blockId || '');
    if (!votesByBlock.has(blockId)) votesByBlock.set(blockId, []);
    votesByBlock.get(blockId).push(vote);
  }

  const ordered = [...blocks].sort((left, right) => Number(left.height || 0) - Number(right.height || 0));
  const chainIssuesByBlock = new Map();
  let previousHash = 'GENESIS';
  for (const block of ordered) {
    const issues = [];
    if (block.previousHash !== previousHash) issues.push('PREVIOUS_HASH_MISMATCH');
    const recalculated = new ChainBlock({
      height: block.height,
      previousHash: block.previousHash,
      proofIds: block.proofIds,
      transactionIds: block.transactionIds,
      rewardIds: block.rewardIds || [],
      proofHashes: block.proofHashes,
      txHashes: block.txHashes,
      rewardHashes: block.rewardHashes || [],
      merkleRoot: block.merkleRoot,
      createdAt: block.createdAt
    });
    if (recalculated.blockHash !== block.blockHash) issues.push('BLOCK_HASH_MISMATCH');
    if (issues.length) chainIssuesByBlock.set(String(block.id), issues);
    previousHash = block.blockHash;
  }

  const plans = [];
  for (const block of ordered) {
    if (!TARGET_STATUSES.has(String(block.status || ''))) continue;
    const missingReferences = [];
    for (const [type, ids] of [
      ['proof', block.proofIds || []],
      ['transaction', block.transactionIds || []],
      ['reward', block.rewardIds || []]
    ]) {
      for (const id of ids) {
        if (!knownReferences[type].has(String(id))) missingReferences.push({ type, id: String(id) });
      }
    }

    const voteRows = votesByBlock.get(String(block.id)) || [];
    const assignedValidatorIds = Array.isArray(block.validation?.assignedValidatorIds)
      ? block.validation.assignedValidatorIds
      : [];
    const validationStarted = assignedValidatorIds.length > 0
      || Number(block.validation?.requiredVotes || 0) > 0
      || voteRows.length > 0;
    const reasons = [
      ...(chainIssuesByBlock.get(String(block.id)) || []),
      ...(missingReferences.length ? ['MISSING_LEDGER_REFERENCE'] : []),
      ...(validationStarted ? ['PARTIAL_VALIDATION_STATE'] : [])
    ];
    const quarantine = reasons.length > 0;
    plans.push({
      blockId: block.id,
      height: block.height,
      blockHash: block.blockHash,
      originalStatus: block.status,
      reason: block.reason || '',
      createdAt: block.createdAt,
      disposition: quarantine ? 'QUARANTINE_LEGACY_MOCK' : 'ARCHIVE_UNVALIDATED_LEGACY_MOCK',
      dispositionReasons: quarantine ? reasons : ['NO_VALIDATOR_ASSIGNMENT_OR_VOTES'],
      assignedValidatorCount: assignedValidatorIds.length,
      requiredVotes: Number(block.validation?.requiredVotes || 0),
      approveVotes: Number(block.validation?.approveVotes || 0),
      rejectVotes: Number(block.validation?.rejectVotes || 0),
      voteRows: voteRows.length,
      missingReferences
    });
  }

  const minStake = Math.max(0, Number(process.env.AISFOR_CHAIN_MIN_VALIDATOR_STAKE || 1));
  const eligibleValidators = (state.nodes || []).filter((node) => (
    ['active', 'warning'].includes(String(node.status || '').toLowerCase())
      && Number(node.lockedPoint || 0) >= minStake
  ));
  const fingerprint = sha256(ordered.map((block) => (
    `${block.id}:${block.height}:${block.blockHash}:${block.status}`
  )).join('|'));
  const archiveCount = plans.filter((row) => row.disposition === 'ARCHIVE_UNVALIDATED_LEGACY_MOCK').length;
  const quarantineCount = plans.length - archiveCount;
  const existingQuarantine = blocks
    .filter((row) => row.status === 'QUARANTINED_LEGACY_MOCK')
    .map((row) => ({
      blockId: row.id,
      height: row.height,
      dispositionReasons: row.legacyDisposition?.reasons || ['LEGACY_QUARANTINE'],
      missingReferences: row.legacyDisposition?.missingReferences || []
    }));

  return {
    ok: chainIssuesByBlock.size === 0,
    mode: 'legacy-mock-finality-backlog-audit',
    auditVersion: 1,
    checkedAt: new Date().toISOString(),
    fingerprint,
    mockBlockWritesLocked: process.env.AISFOR_CHAIN_MOCK_BLOCK_WRITES_ENABLED === '0',
    safeToAutoFinalize: false,
    autoFinalizeBlockers: [
      'The backlog has no validator assignments or votes.',
      `Only ${eligibleValidators.length} eligible legacy mock validator(s) exist.`,
      'Mock finalization would create validator rewards and finalized callbacks.'
    ],
    totals: {
      blocks: blocks.length,
      targetBacklog: plans.length,
      archiveUnvalidated: archiveCount,
      quarantine: quarantineCount,
      archivedLegacy: blocks.filter((row) => row.status === 'ARCHIVED_LEGACY_MOCK').length,
      quarantinedLegacy: existingQuarantine.length,
      finalizedLegacy: blocks.filter((row) => row.status === 'FINALIZED_MOCK').length,
      validatorVotes: votes.length,
      eligibleLegacyValidators: eligibleValidators.length,
      chainIssues: chainIssuesByBlock.size,
      missingReferences: plans.reduce((sum, row) => sum + row.missingReferences.length, 0)
    },
    statusCounts: countBy(blocks, (row) => row.status),
    reasonCounts: countBy(plans, (row) => row.reason),
    dispositionCounts: countBy(plans, (row) => row.disposition),
    existingQuarantine,
    plans,
    reportPath
  };
}

function summary(audit) {
  return {
    ok: audit.ok,
    mode: audit.mode,
    checkedAt: audit.checkedAt,
    fingerprint: audit.fingerprint,
    mockBlockWritesLocked: audit.mockBlockWritesLocked,
    safeToAutoFinalize: audit.safeToAutoFinalize,
    totals: audit.totals,
    statusCounts: audit.statusCounts,
    dispositionCounts: audit.dispositionCounts,
    quarantineBlocks: audit.plans
      .filter((row) => row.disposition === 'QUARANTINE_LEGACY_MOCK')
      .map((row) => ({
        blockId: row.blockId,
        height: row.height,
        dispositionReasons: row.dispositionReasons,
        missingReferences: row.missingReferences
      }))
      .concat(audit.existingQuarantine || []),
    reportPath: audit.reportPath
  };
}

function main() {
  const audit = buildAudit(persistentStore.readState());
  if (process.argv.includes('--write-report')) writeJsonAtomic(reportPath, audit);
  console.log(JSON.stringify(summary(audit), null, 2));
  if (!audit.ok || !audit.mockBlockWritesLocked) process.exitCode = 1;
}

if (require.main === module) main();

module.exports = {
  TARGET_STATUSES,
  buildAudit,
  reportPath,
  summary,
  writeJsonAtomic
};
