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

const CHAIN_SEED_VERSION = 'mazex-chain-seed-1.0';
const ZERO_HASH = '0x00';

function normalizeHash(value, fallback = ZERO_HASH) {
  if (value === undefined || value === null || value === '') return fallback;
  return String(value);
}

function stripDerivedFields(source = {}) {
  const { source_hash, step_seed, ...payload } = source;
  return payload;
}

function finalizeSeedSource(source = {}) {
  const payload = {
    version: CHAIN_SEED_VERSION,
    ...stripDerivedFields(source),
  };
  const sourceHash = sha256(payload);
  return {
    ...payload,
    source_hash: sourceHash,
    step_seed: sha256({
      type: 'mazex.step_seed',
      version: CHAIN_SEED_VERSION,
      source_hash: sourceHash,
    }),
  };
}

function genesisStepSeedSource({
  chainId = 'mazexchain-local',
  seasonSeed = 'mazex-expression-tree-season',
  algorithm = 'mazex-coordinate-expression-tree-2.0',
  layerNo = 1,
  stepNo = 1,
  genesisHash = ZERO_HASH,
  entropyHash = ZERO_HASH,
} = {}) {
  return finalizeSeedSource({
    type: 'GENESIS_STEP_SEED',
    chain_id: chainId,
    season_seed: seasonSeed,
    algorithm,
    layer_no: Math.max(1, Math.trunc(Number(layerNo) || 1)),
    step_no: Math.max(1, Math.trunc(Number(stepNo) || 1)),
    previous_seed: ZERO_HASH,
    previous_solution_hash: ZERO_HASH,
    previous_behavior_hash: ZERO_HASH,
    previous_validation_id: ZERO_HASH,
    previous_block_hash: normalizeHash(genesisHash),
    entropy_hash: normalizeHash(entropyHash),
  });
}

function deriveStepSeedSource({
  chainId = 'mazexchain-local',
  seasonSeed = 'mazex-expression-tree-season',
  algorithm = 'mazex-coordinate-expression-tree-2.0',
  layerNo,
  stepNo,
  previousSeed,
  previousSolutionHash,
  previousBehaviorHash,
  previousValidationId,
  previousBlockHash,
  entropyHash = ZERO_HASH,
} = {}) {
  if (!Number.isInteger(Number(layerNo)) || Number(layerNo) < 1) {
    throw new Error('layerNo must be a positive integer');
  }
  if (!Number.isInteger(Number(stepNo)) || Number(stepNo) < 1) {
    throw new Error('stepNo must be a positive integer');
  }
  return finalizeSeedSource({
    type: 'CHAINED_STEP_SEED',
    chain_id: chainId,
    season_seed: seasonSeed,
    algorithm,
    layer_no: Math.trunc(Number(layerNo)),
    step_no: Math.trunc(Number(stepNo)),
    previous_seed: normalizeHash(previousSeed),
    previous_solution_hash: normalizeHash(previousSolutionHash),
    previous_behavior_hash: normalizeHash(previousBehaviorHash),
    previous_validation_id: normalizeHash(previousValidationId),
    previous_block_hash: normalizeHash(previousBlockHash),
    entropy_hash: normalizeHash(entropyHash),
  });
}

function deriveNextStepSeedSource({
  chainId = 'mazexchain-local',
  seasonSeed = 'mazex-expression-tree-season',
  algorithm = 'mazex-coordinate-expression-tree-2.0',
  previousStep,
  previousResult,
  previousBlockHash,
  nextLayerNo,
  nextStepNo,
  entropyHash = ZERO_HASH,
} = {}) {
  if (!previousStep) throw new Error('previousStep is required');
  if (!previousResult) throw new Error('previousResult is required');
  return deriveStepSeedSource({
    chainId,
    seasonSeed,
    algorithm,
    layerNo: nextLayerNo,
    stepNo: nextStepNo,
    previousSeed: previousStep.seed || previousStep.step_seed || previousStep.seed_source?.step_seed,
    previousSolutionHash:
      previousResult.solution_hash ||
      previousResult.answer_hash ||
      previousResult.reveal_hash ||
      previousResult.candidate_hash,
    previousBehaviorHash: previousResult.behavior_hash,
    previousValidationId: previousResult.validation_id,
    previousBlockHash,
    entropyHash,
  });
}

function seedSourceCommitment(seedSource) {
  const finalized = finalizeSeedSource(seedSource);
  return {
    version: CHAIN_SEED_VERSION,
    layer_no: finalized.layer_no,
    step_no: finalized.step_no,
    source_hash: finalized.source_hash,
    step_seed_hash: sha256(finalized.step_seed),
    previous_solution_hash: finalized.previous_solution_hash,
    previous_behavior_hash: finalized.previous_behavior_hash,
    previous_validation_id: finalized.previous_validation_id,
    previous_block_hash: finalized.previous_block_hash,
    entropy_hash: finalized.entropy_hash,
  };
}

function assertSeedSourceForStep(seedSource, { layerNo, stepNo, algorithm } = {}) {
  const finalized = finalizeSeedSource(seedSource);
  if (layerNo !== undefined && Number(finalized.layer_no) !== Number(layerNo)) {
    throw new Error(`seed source layer mismatch: ${finalized.layer_no} != ${layerNo}`);
  }
  if (stepNo !== undefined && Number(finalized.step_no) !== Number(stepNo)) {
    throw new Error(`seed source step mismatch: ${finalized.step_no} != ${stepNo}`);
  }
  if (algorithm !== undefined && finalized.algorithm !== algorithm) {
    throw new Error(`seed source algorithm mismatch: ${finalized.algorithm} != ${algorithm}`);
  }
  return finalized;
}

module.exports = {
  CHAIN_SEED_VERSION,
  ZERO_HASH,
  assertSeedSourceForStep,
  deriveNextStepSeedSource,
  deriveStepSeedSource,
  finalizeSeedSource,
  genesisStepSeedSource,
  seedSourceCommitment,
};
