const { merkleRoot, sha256 } = require('./hash');

const LOCAL_CHAIN_VERSION = 'mazex-local-chain-1.0';
const GENESIS_PREVIOUS_HASH = '0x00';

function nowIso() {
  return new Date().toISOString();
}

function safeArray(value) {
  return Array.isArray(value) ? value : [];
}

function safeScore(value) {
  const numeric = Math.trunc(Number(value || 0));
  return Number.isFinite(numeric) && numeric > 0 ? numeric : 0;
}

function transactionHash(tx) {
  return sha256({
    version: LOCAL_CHAIN_VERSION,
    type: tx.type,
    nonce: tx.nonce,
    created_at: tx.created_at,
    payload: tx.payload,
  });
}

function createTransaction({ type, payload = {}, nonce = 0, createdAt = nowIso() }) {
  const tx = {
    version: LOCAL_CHAIN_VERSION,
    type,
    nonce,
    created_at: createdAt,
    payload,
  };
  return {
    ...tx,
    tx_hash: transactionHash(tx),
  };
}

function compactSolvedStep(step) {
  return {
    layer_no: step.layer_no,
    step_no: step.step_no,
    solver: step.solver,
    auth_mode: step.auth_mode,
    wallet_address: step.wallet_address,
    validation_id: step.validation_id,
    answer_hash: step.answer_hash,
    reveal_hash: step.reveal_hash,
    behavior_hash: step.behavior_hash,
    behavior_root: step.behavior_root,
    fragment_registry_root: step.fragment_registry_root,
    point_set_hash: step.point_set_hash,
    seed_source_hash: step.seed_source_hash,
    next_seed_source_hash: step.next_seed_source_hash,
    reward_amount: step.reward_amount,
    difficulty_score: step.difficulty_score,
    work_score: step.work_score,
    solved_at: step.solved_at,
  };
}

function stateRootPayload(state) {
  return {
    version: state.version,
    current_layer: state.currentLayer,
    current_step: state.currentStep,
    grid_size: state.gridSize,
    layer_root: state.layerRoot,
    current_formula_hash: state.currentFormulaHash || null,
    current_step_commit: state.currentStepCommitment?.step_commit || null,
    current_seed_source_hash: state.currentSeedSourceHash || null,
    current_fragment_registry_root:
      state.fragmentRegistry?.active_root || state.currentStepCommitment?.fragment_registry_root || null,
    allocation_layer: state.allocation?.layer_no,
    allocation_step_count: state.allocation?.step_count,
    solved_steps: safeArray(state.solvedSteps).map(compactSolvedStep),
    leaderboard: state.leaderboard || {},
    accounts: state.accounts || {},
    burned_fees: state.burnedFees || 0,
    reward_ledger: safeArray(state.rewardLedger).map((reward) => ({
      id: reward.id,
      type: reward.type,
      status: reward.status,
      recipient: reward.recipient,
      amount: reward.amount,
      layer_no: reward.layer_no,
      step_no: reward.step_no,
      block_height: reward.block_height,
      reward_tx_hash: reward.reward_tx_hash,
      confirmations: reward.confirmations,
      confirmations_required: reward.confirmations_required,
      work_after: reward.work_after,
      work_required_after: reward.work_required_after,
      confirmed_at: reward.confirmed_at || null,
    })),
    transfers: safeArray(state.transfers).map((transfer) => ({
      id: transfer.id,
      from: transfer.from,
      to: transfer.to,
      amount: transfer.amount,
      fee: transfer.fee,
      nonce: transfer.nonce,
      block_height: transfer.block_height,
      tx_hash: transfer.tx_hash,
      created_at: transfer.created_at,
    })),
  };
}

function computeStateRoot(state) {
  return sha256({
    type: 'mazex.state_root',
    payload: stateRootPayload(state),
  });
}

function blockHash(block) {
  return sha256({
    version: block.version,
    height: block.height,
    previous_block_hash: block.previous_block_hash,
    created_at: block.created_at,
    producer: block.producer,
    work_score: block.work_score || 0,
    cumulative_work_score: block.cumulative_work_score || 0,
    tx_root: block.tx_root,
    state_root: block.state_root,
    state_after: block.state_after,
  });
}

function transactionWorkScore(tx) {
  const type = String(tx?.type || '');
  if (!['SOLVE_STEP', 'BASE1_SOLVE_STEP'].includes(type)) return 0;
  const payload = tx.payload || {};
  return safeScore(payload.work_score || payload.difficulty_score || payload.score);
}

function blockWorkScore(transactions = []) {
  return safeArray(transactions).reduce((sum, tx) => sum + transactionWorkScore(tx), 0);
}

function expectedCumulativeWorkScore(block, previousBlock = null) {
  return safeScore(previousBlock?.cumulative_work_score) + safeScore(block?.work_score);
}

function blockStateAfter(state) {
  return {
    current_layer: state.currentLayer,
    current_step: state.currentStep,
    grid_size: state.gridSize,
    layer_root: state.layerRoot,
    current_formula_hash: state.currentFormulaHash || null,
    current_seed_source_hash: state.currentSeedSourceHash || null,
    current_fragment_registry_root:
      state.fragmentRegistry?.active_root || state.currentStepCommitment?.fragment_registry_root || null,
    solved_count: safeArray(state.solvedSteps).length,
    leaderboard_size: Object.keys(state.leaderboard || {}).length,
    account_count: Object.keys(state.accounts || {}).length,
    pending_reward_count: safeArray(state.rewardLedger).filter((reward) => reward.status === 'pending').length,
    confirmed_reward_count: safeArray(state.rewardLedger).filter((reward) => reward.status === 'confirmed').length,
    transfer_count: safeArray(state.transfers).length,
    burned_fees: state.burnedFees || 0,
  };
}

function createBlock({
  height,
  previousBlockHash,
  transactions = [],
  state,
  producer = 'local-producer',
  createdAt = nowIso(),
}) {
  const txs = transactions.map((tx, index) => {
    if (tx.tx_hash) return tx;
    return createTransaction({
      type: tx.type,
      payload: tx.payload,
      nonce: tx.nonce ?? index,
      createdAt: tx.created_at || createdAt,
    });
  });
  const txRoot = merkleRoot(txs.map((tx) => tx.tx_hash));
  const stateRoot = computeStateRoot(state);
  const workScore = blockWorkScore(txs);
  const previousBlock = state?.chain?.blocks?.[state.chain.blocks.length - 1] || null;
  const cumulativeWorkScore = safeScore(previousBlock?.cumulative_work_score) + workScore;
  const block = {
    version: LOCAL_CHAIN_VERSION,
    height,
    previous_block_hash: previousBlockHash,
    created_at: createdAt,
    producer,
    work_score: workScore,
    cumulative_work_score: cumulativeWorkScore,
    tx_count: txs.length,
    tx_root: txRoot,
    state_root: stateRoot,
    state_after: blockStateAfter(state),
    transactions: txs,
  };
  return {
    ...block,
    block_hash: blockHash(block),
  };
}

function genesisTransactions(state, createdAt = nowIso()) {
  return [
    createTransaction({
      type: 'GENESIS',
      nonce: 0,
      createdAt,
      payload: {
        chain: 'mazexchain-local',
        layer_no: state.currentLayer,
        step_no: state.currentStep,
        grid_size: state.gridSize,
      },
    }),
    createTransaction({
      type: 'LAYER_COMMITTED',
      nonce: 1,
      createdAt,
      payload: {
        layer_no: state.currentLayer,
        step_count: state.allocation?.step_count,
        layer_root: state.layerRoot,
      },
    }),
    createTransaction({
      type: 'STEP_COMMITTED',
      nonce: 2,
      createdAt,
      payload: {
        layer_no: state.currentLayer,
        step_no: state.currentStep,
        step_commit: state.currentStepCommitment?.step_commit || state.commitmentsByStep?.[String(state.currentStep)]?.step_commit || null,
        formula_hash: state.currentFormulaHash || state.commitmentsByStep?.[String(state.currentStep)]?.formula_hash || null,
        fragment_registry_root:
          state.fragmentRegistry?.active_root || state.currentStepCommitment?.fragment_registry_root || null,
        verification_hash: state.commitmentsByStep?.[String(state.currentStep)]?.verification_hash || null,
      },
    }),
  ];
}

function createGenesisBlock(state, createdAt = nowIso()) {
  return createBlock({
    height: 0,
    previousBlockHash: GENESIS_PREVIOUS_HASH,
    transactions: genesisTransactions(state, createdAt),
    state,
    producer: 'genesis',
    createdAt,
  });
}

function upgradeChainWorkScores(chain) {
  const blocks = safeArray(chain?.blocks);
  if (!blocks.length) return chain;
  let previous = null;
  for (const block of blocks) {
    block.work_score = blockWorkScore(block.transactions);
    block.cumulative_work_score = safeScore(previous?.cumulative_work_score) + block.work_score;
    if (previous) block.previous_block_hash = previous.block_hash;
    block.block_hash = blockHash(block);
    previous = block;
  }
  const head = blocks[blocks.length - 1];
  chain.genesis_hash = blocks[0].block_hash;
  chain.head_hash = head.block_hash;
  chain.height = head.height;
  return chain;
}

function initializeChain(state, createdAt = nowIso()) {
  const genesis = createGenesisBlock(state, createdAt);
  return {
    version: LOCAL_CHAIN_VERSION,
    network: 'local',
    genesis_hash: genesis.block_hash,
    head_hash: genesis.block_hash,
    height: genesis.height,
    blocks: [genesis],
  };
}

function ensureChain(state) {
  if (!state.chain || !Array.isArray(state.chain.blocks) || !state.chain.blocks.length) {
    state.chain = initializeChain(state);
  }
  upgradeChainWorkScores(state.chain);
  return state.chain;
}

function appendBlock(state, { transactions = [], producer = 'local-producer', createdAt = nowIso() } = {}) {
  const chain = ensureChain(state);
  const previous = chain.blocks[chain.blocks.length - 1];
  const block = createBlock({
    height: previous.height + 1,
    previousBlockHash: previous.block_hash,
    transactions,
    state,
    producer,
    createdAt,
  });
  chain.blocks.push(block);
  chain.height = block.height;
  chain.head_hash = block.block_hash;
  return block;
}

function chainSummary(chain) {
  if (!chain || !Array.isArray(chain.blocks) || !chain.blocks.length) {
    return {
      version: LOCAL_CHAIN_VERSION,
      height: -1,
      head_hash: null,
      block_count: 0,
      cumulative_work_score: 0,
    };
  }
  const head = chain.blocks[chain.blocks.length - 1];
  return {
    version: chain.version || LOCAL_CHAIN_VERSION,
    network: chain.network || 'local',
    height: chain.height,
    head_hash: chain.head_hash,
    genesis_hash: chain.genesis_hash,
    block_count: chain.blocks.length,
    cumulative_work_score: safeScore(head.cumulative_work_score),
    head_work_score: safeScore(head.work_score),
    head_state_root: head.state_root,
    head_tx_root: head.tx_root,
    head_tx_count: head.tx_count,
  };
}

function getBlocks(chain, limit = 20) {
  return safeArray(chain?.blocks).slice(-Math.max(1, Math.trunc(Number(limit) || 20))).reverse();
}

function verifyBlock(block, previousBlock = null) {
  for (const tx of safeArray(block.transactions)) {
    if (tx.tx_hash !== transactionHash(tx)) {
      return { ok: false, code: 'TX_HASH_MISMATCH', height: block.height, tx_hash: tx.tx_hash };
    }
  }
  const expectedTxRoot = merkleRoot(safeArray(block.transactions).map((tx) => tx.tx_hash));
  if (block.tx_root !== expectedTxRoot) {
    return { ok: false, code: 'TX_ROOT_MISMATCH', height: block.height };
  }
  const expectedWorkScore = blockWorkScore(block.transactions);
  if (safeScore(block.work_score) !== expectedWorkScore) {
    return {
      ok: false,
      code: 'WORK_SCORE_MISMATCH',
      height: block.height,
      expected_work_score: expectedWorkScore,
      actual_work_score: block.work_score,
    };
  }
  const expectedCumulative = expectedCumulativeWorkScore(block, previousBlock);
  if (safeScore(block.cumulative_work_score) !== expectedCumulative) {
    return {
      ok: false,
      code: 'CUMULATIVE_WORK_SCORE_MISMATCH',
      height: block.height,
      expected_cumulative_work_score: expectedCumulative,
      actual_cumulative_work_score: block.cumulative_work_score,
    };
  }
  const expectedHash = blockHash(block);
  if (block.block_hash !== expectedHash) {
    return { ok: false, code: 'BLOCK_HASH_MISMATCH', height: block.height };
  }
  if (previousBlock && block.previous_block_hash !== previousBlock.block_hash) {
    return { ok: false, code: 'PREVIOUS_HASH_MISMATCH', height: block.height };
  }
  return { ok: true, work_score: expectedWorkScore, cumulative_work_score: expectedCumulative };
}

function verifyLocalChain(chain) {
  const blocks = safeArray(chain?.blocks);
  if (!blocks.length) return { ok: false, code: 'EMPTY_CHAIN' };
  for (let index = 0; index < blocks.length; index += 1) {
    const result = verifyBlock(blocks[index], index > 0 ? blocks[index - 1] : null);
    if (!result.ok) return result;
  }
  const head = blocks[blocks.length - 1];
  if (chain.head_hash !== head.block_hash || chain.height !== head.height) {
    return { ok: false, code: 'HEAD_MISMATCH', height: head.height };
  }
  return {
    ok: true,
    height: head.height,
    head_hash: head.block_hash,
    cumulative_work_score: safeScore(head.cumulative_work_score),
  };
}

function compareChainHeads(a, b) {
  const left = a?.blocks ? a.blocks[a.blocks.length - 1] : a;
  const right = b?.blocks ? b.blocks[b.blocks.length - 1] : b;
  if (!left && !right) return 0;
  if (left && !right) return 1;
  if (!left && right) return -1;

  const leftWork = safeScore(left.cumulative_work_score);
  const rightWork = safeScore(right.cumulative_work_score);
  if (leftWork !== rightWork) return leftWork > rightWork ? 1 : -1;

  const leftHeight = safeScore(left.height);
  const rightHeight = safeScore(right.height);
  if (leftHeight !== rightHeight) return leftHeight > rightHeight ? 1 : -1;

  const leftHash = String(left.block_hash || '');
  const rightHash = String(right.block_hash || '');
  if (leftHash === rightHash) return 0;
  if (!leftHash) return -1;
  if (!rightHash) return 1;
  return leftHash < rightHash ? 1 : -1;
}

function selectBestChain(chains = []) {
  const valid = safeArray(chains).filter((chain) => verifyLocalChain(chain).ok);
  if (!valid.length) return null;
  return valid.reduce((best, candidate) => (compareChainHeads(candidate, best) > 0 ? candidate : best), valid[0]);
}

module.exports = {
  GENESIS_PREVIOUS_HASH,
  LOCAL_CHAIN_VERSION,
  appendBlock,
  blockHash,
  blockWorkScore,
  chainSummary,
  compareChainHeads,
  computeStateRoot,
  createBlock,
  createGenesisBlock,
  createTransaction,
  getBlocks,
  initializeChain,
  selectBestChain,
  transactionHash,
  upgradeChainWorkScores,
  verifyBlock,
  verifyLocalChain,
};
