const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const persistentStore = require('./persistentStore');
const ledgerConsistencyService = require('./ledgerConsistencyService');
const ledgerDbReadinessService = require('./ledgerDbReadinessService');
const readBackendSwitchService = require('./ledgerReadBackendSwitchService');
const readMonitorService = require('./ledgerMongoReadMonitorService');
const postSwitchGuardService = require('./ledgerMongoPostSwitchGuardService');

const COLLECTIONS = [
  { key: 'proofs', collection: 'chain_proofs', label: 'Proof Ledger', critical: true },
  { key: 'transactions', collection: 'chain_transactions', label: 'Chain Transactions', critical: true },
  { key: 'blocks', collection: 'chain_blocks', label: 'Blocks', critical: true },
  { key: 'callbackEvents', collection: 'chain_callbacks', label: 'Callbacks', critical: false },
  { key: 'nodes', collection: 'node_participants', label: 'Node Participants', critical: true },
  { key: 'nodeEconomyEntries', collection: 'node_economy_ledger', label: 'Node Economy Ledger', critical: false },
  { key: 'pointEntries', collection: 'point_ledger', label: 'Point Ledger', critical: false },
  { key: 'validatorVotes', collection: 'validator_votes', label: 'Validator Votes', critical: false },
  { key: 'backupIndexes', collection: 'backup_indexes', label: 'Backup Indexes', critical: true },
  { key: 'distributedChunks', collection: 'distributed_chunks', label: 'Distributed Chunks', critical: true },
  { key: 'wallets', collection: 'wallets', label: 'Wallets', critical: true },
  { key: 'walletTransactions', collection: 'wallet_transactions', label: 'Wallet Transactions', critical: true },
  { key: 'rewardEntries', collection: 'reward_ledger', label: 'Reward Ledger', critical: false },
  { key: 'rewardAuditEntries', collection: 'reward_audit_log', label: 'Reward Audit Log', critical: false },
  { key: 'rewardReviewDecisions', collection: 'reward_review_decisions', label: 'Reward Review Decisions', critical: false }
];

function maskValue(value) {
  if (!value) return '';
  const text = String(value);
  if (text.length <= 10) return 'configured';
  return `${text.slice(0, 6)}...${text.slice(-4)}`;
}

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

function stableStringify(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
}

function pickNaturalId(row) {
  if (!row || typeof row !== 'object') return '';
  return row.id || row.txHash || row.proofId || row.blockHash || row.address || row.walletId || row.nodeId || row.backupId || row.chunkId || row.eventId || row.refId || '';
}

function getDryRunPath() {
  const storageInfo = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {};
  return path.join(storageInfo.dataDir || path.dirname(persistentStore.getLedgerPath()), 'migration-dry-run.json');
}

function getFileInfo(ledgerPath) {
  try {
    const stat = fs.statSync(ledgerPath);
    return { exists: true, sizeBytes: stat.size, updatedAt: stat.mtime.toISOString() };
  } catch (err) {
    return { exists: false, sizeBytes: 0, updatedAt: null };
  }
}

function getTmpFiles(ledgerPath) {
  const dir = path.dirname(ledgerPath);
  const base = path.basename(ledgerPath);
  try {
    return fs.readdirSync(dir).filter((name) => name.startsWith(`${base}.tmp`) || name.includes('.broken-'));
  } catch (err) {
    return [];
  }
}

function countRows(state) {
  return COLLECTIONS.map((item) => {
    const rows = Array.isArray(state[item.key]) ? state[item.key] : [];
    return { ...item, count: rows.length, ready: Array.isArray(state[item.key]) };
  });
}

function buildChecks({ fileInfo, tmpFiles, collections, dbUri }) {
  const criticalMissing = collections.filter((row) => row.critical && !row.ready);
  const criticalCount = collections.filter((row) => row.critical).length;
  const criticalEmpty = collections.filter((row) => row.critical && row.count === 0);

  return [
    {
      key: 'json_file_readable',
      label: '현재 JSON ledger 읽기',
      ok: fileInfo.exists,
      detail: fileInfo.exists ? 'ledger.json을 정상으로 읽을 수 있습니다.' : 'ledger.json 파일이 아직 없습니다.'
    },
    {
      key: 'state_shape_normalized',
      label: '컬렉션 구조 정규화',
      ok: criticalMissing.length === 0,
      detail: criticalMissing.length === 0 ? 'DB로 나눌 주요 컬렉션 배열이 준비되어 있습니다.' : `${criticalMissing.length}개 주요 컬렉션 구조 확인이 필요합니다.`
    },
    {
      key: 'critical_data_exists',
      label: '주요 데이터 존재',
      ok: criticalEmpty.length < criticalCount,
      detail: criticalEmpty.length === 0 ? '주요 데이터가 모두 존재합니다.' : `${criticalEmpty.length}개 주요 컬렉션은 아직 비어 있습니다.`
    },
    {
      key: 'no_recovery_files',
      label: '임시/손상 파일 점검',
      ok: tmpFiles.length === 0,
      detail: tmpFiles.length === 0 ? '남은 tmp/broken 파일이 없습니다.' : `${tmpFiles.length}개 점검 파일이 있습니다.`
    },
    {
      key: 'db_uri_configured',
      label: 'DB 연결값 준비',
      ok: Boolean(dbUri),
      detail: dbUri ? 'DB URI 환경변수가 설정되어 있습니다.' : '아직 DB URI가 없습니다. 전환 전 환경변수부터 정해야 합니다.'
    }
  ];
}

function buildDocument(item, row, index, createdAt) {
  const naturalId = pickNaturalId(row);
  const sourceHash = sha256(stableStringify(row));
  const migrationId = sha256(`${item.key}:${naturalId || index}:${sourceHash}`);

  return {
    _migrationId: migrationId,
    _targetCollection: item.collection,
    _sourceKey: item.key,
    _sourceIndex: index,
    _sourceNaturalId: naturalId,
    _sourceHash: sourceHash,
    migratedAt: createdAt,
    payload: row
  };
}

function buildCollectionDocuments(state, createdAt = new Date().toISOString()) {
  return COLLECTIONS.map((item) => {
    const rows = Array.isArray(state[item.key]) ? state[item.key] : [];
    const docs = rows.map((row, index) => buildDocument(item, row, index, createdAt));
    return { ...item, docs };
  });
}

function buildDryRun(options = {}) {
  const persist = Boolean(options.persist);
  const state = persistentStore.readState();
  const createdAt = new Date().toISOString();
  const seen = new Set();
  const warnings = [];
  const collections = buildCollectionDocuments(state, createdAt).map((item) => {
    const docs = item.docs;
    const duplicateIds = [];

    for (const doc of docs) {
      if (seen.has(doc._migrationId)) duplicateIds.push(doc._migrationId);
      seen.add(doc._migrationId);
    }

    if (item.critical && docs.length === 0) warnings.push(`${item.label} has no rows yet.`);
    if (duplicateIds.length) warnings.push(`${item.label} has ${duplicateIds.length} duplicate migration ids.`);

    return {
      key: item.key,
      collection: item.collection,
      label: item.label,
      critical: item.critical,
      count: docs.length,
      duplicateIds: duplicateIds.slice(0, 5),
      sampleIds: docs.slice(0, 3).map((doc) => doc._migrationId.slice(0, 16)),
      sampleSourceHashes: docs.slice(0, 3).map((doc) => doc._sourceHash.slice(0, 16))
    };
  });

  const result = {
    ok: warnings.length === 0,
    mode: 'dry-run-only',
    backendSwitchLocked: true,
    dbWritesEnabled: false,
    createdAt,
    dryRunPath: getDryRunPath(),
    totalDocuments: collections.reduce((sum, row) => sum + row.count, 0),
    warningCount: warnings.length,
    warnings,
    collections
  };

  if (persist) {
    fs.writeFileSync(result.dryRunPath, `${JSON.stringify(result, null, 2)}\n`);
  }

  return result;
}

function getStagingSummaryPath() {
  const storageInfo = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {};
  return path.join(storageInfo.dataDir || path.dirname(persistentStore.getLedgerPath()), 'migration-staging-latest.json');
}

function getStagingComparePath() {
  const storageInfo = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {};
  return path.join(storageInfo.dataDir || path.dirname(persistentStore.getLedgerPath()), 'migration-staging-compare.json');
}

function readJsonFile(filePath, fallback) {
  try {
    const stat = fs.statSync(filePath);
    return { ...JSON.parse(fs.readFileSync(filePath, 'utf8')), exists: true, fileUpdatedAt: stat.mtime.toISOString() };
  } catch (err) {
    return { ...fallback, exists: false, fileUpdatedAt: null, path: filePath };
  }
}

function getLatestStaging() {
  const summaryPath = getStagingSummaryPath();
  const comparePath = getStagingComparePath();
  return {
    summary: readJsonFile(summaryPath, {
      ok: false,
      mode: 'not-run',
      path: summaryPath,
      totalDocuments: 0,
      collections: []
    }),
    compare: readJsonFile(comparePath, {
      ok: false,
      mode: 'not-run',
      path: comparePath,
      totalExpected: 0,
      totalActual: 0,
      failedCollections: 0,
      rows: []
    })
  };
}
function getLatestDryRun() {
  const dryRunPath = getDryRunPath();
  try {
    const stat = fs.statSync(dryRunPath);
    const dryRun = JSON.parse(fs.readFileSync(dryRunPath, 'utf8'));
    return { ...dryRun, exists: true, fileUpdatedAt: stat.mtime.toISOString() };
  } catch (err) {
    return {
      exists: false,
      ok: false,
      mode: 'not-run',
      dryRunPath,
      totalDocuments: 0,
      warningCount: 0,
      warnings: [],
      collections: []
    };
  }
}

function getLatestMongoReadBackend() {
  const storageInfo = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {};
  const dataDir = storageInfo.dataDir || path.dirname(persistentStore.getLedgerPath());
  return {
    compare: readJsonFile(path.join(dataDir, 'mongo-read-backend-compare.json'), {
      ok: false,
      mode: 'not-run',
      totalExpected: 0,
      totalActual: 0,
      failedCollections: 0,
      rows: [],
      path: path.join(dataDir, 'mongo-read-backend-compare.json')
    }),
    liveBackfill: readJsonFile(path.join(dataDir, 'mongo-live-backfill-latest.json'), {
      ok: false,
      mode: 'not-run',
      writesLiveCollections: false,
      totalDocuments: 0,
      collections: [],
      path: path.join(dataDir, 'mongo-live-backfill-latest.json')
    })
  };
}
function getStatus() {
  const state = persistentStore.readState();
  const storageInfo = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {
    backend: 'json-file',
    ledgerPath: persistentStore.getLedgerPath()
  };
  const ledgerPath = storageInfo.ledgerPath || persistentStore.getLedgerPath();
  const fileInfo = getFileInfo(ledgerPath);
  const tmpFiles = getTmpFiles(ledgerPath);
  const collections = countRows(state);
  const dbUri = process.env.AISFOR_CHAIN_LEDGER_DB_URI || process.env.DATABASE_URL || process.env.MONGODB_URI || '';
  const targetBackend = process.env.AISFOR_CHAIN_LEDGER_TARGET_BACKEND || 'mongodb';
  const checks = buildChecks({ fileInfo, tmpFiles, collections, dbUri });
  const okChecks = checks.filter((row) => row.ok).length;

  return {
    currentBackend: storageInfo.backend || 'json-file',
    targetBackend,
    canSwitchBackend: false,
    mode: 'preparation',
    ledgerPath,
    dataDir: storageInfo.dataDir || path.dirname(ledgerPath),
    fileInfo,
    tmpFiles,
    collections,
    totalRows: collections.reduce((sum, row) => sum + row.count, 0),
    checks,
    readinessScore: Math.round((okChecks / checks.length) * 100),
    dryRun: getLatestDryRun(),
    staging: getLatestStaging(),
    consistency: ledgerConsistencyService.getStatus(),
    dbReadiness: ledgerDbReadinessService.getStatus(),
    mongoObservation: storageInfo.mongoObservation || { requested: false, writeEnabled: false, totalDocuments: 0, compare: { ok: false, totalExpected: 0, totalActual: 0, failedCollections: 0, rows: [] } },
    mongoReadBackend: { ...(storageInfo.mongoReadBackend || { requested: false, approved: false, enabled: false, mode: 'json-read-active', reason: 'MongoDB read backend is not requested.' }), ...(storageInfo.mongoReadSnapshot ? { snapshot: storageInfo.mongoReadSnapshot } : {}), ...(storageInfo.mongoLiveWriter ? { liveWriter: storageInfo.mongoLiveWriter } : {}), ...getLatestMongoReadBackend() },
    mongoReadMonitor: readMonitorService.getStatus(),
    mongoPostSwitchGuard: postSwitchGuardService.getStatus(),
    readBackendSwitch: readBackendSwitchService.getStatus(),
    dualWrite: {
      requested: Boolean(storageInfo.dualWriteRequested),
      dbWriteEnabled: Boolean(storageInfo.dbWriteEnabled),
      active: Boolean(storageInfo.dbAdapter && storageInfo.dbAdapter.active),
      shadowDir: storageInfo.dbAdapter ? storageInfo.dbAdapter.shadowDir : '',
      latestSyncedAt: storageInfo.dbAdapter ? storageInfo.dbAdapter.latestSyncedAt : null,
      totalDocuments: storageInfo.dbAdapter ? storageInfo.dbAdapter.totalDocuments : 0,
      detail: 'DB backend 전환은 잠금 상태입니다. dual-write는 명시적 환경변수에서만 shadow adapter로 동작합니다.'
    },
    db: {
      configured: Boolean(dbUri),
      uriPreview: maskValue(dbUri),
      envKeys: ['AISFOR_CHAIN_LEDGER_DB_URI', 'DATABASE_URL', 'MONGODB_URI']
    },
    nextSteps: [
      'DB 종류와 운영 URI를 확정한다.',
      'JSON ledger를 컬렉션별로 백필하는 dry-run 결과를 확인한다.',
      'MongoDB observation dual-write를 켜고 JSON과 DB observation 컬렉션을 동시에 기록한다.',
      '검증 후 read backend를 DB로 전환한다.',
      'JSON ledger는 rollback snapshot으로만 보관한다.'
    ],
    updatedAt: new Date().toISOString()
  };
}

module.exports = {
  COLLECTIONS,
  buildDocument,
  buildCollectionDocuments,
  getLatestStaging,
  buildDryRun,
  getLatestDryRun,
  getStatus
};











