const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const persistentStore = require('./persistentStore');
const mongoReadAdapter = require('./mongoLedgerReadAdapter');

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 getDataDir() {
  const info = persistentStore.getStorageInfo ? persistentStore.getStorageInfo() : {};
  return info.dataDir || path.dirname(info.ledgerPath || persistentStore.getLedgerPath());
}

function comparePath() {
  return path.join(getDataDir(), 'mongo-read-backend-compare.json');
}

function monitorPath() {
  return path.join(getDataDir(), 'mongo-read-monitor.json');
}

function readJson(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 writeJson(filePath, body) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, `${JSON.stringify(body, null, 2)}\n`);
}

function compareStates(jsonState, mongoState) {
  const rows = mongoReadAdapter.COLLECTIONS.map((item) => {
    const expectedRows = Array.isArray(jsonState[item.key]) ? jsonState[item.key] : [];
    const actualRows = Array.isArray(mongoState[item.key]) ? mongoState[item.key] : [];
    const expectedHashes = expectedRows.map((row) => sha256(stableStringify(row)));
    const actualHashes = actualRows.map((row) => sha256(stableStringify(row)));
    const mismatches = [];
    const max = Math.max(expectedHashes.length, actualHashes.length);

    for (let i = 0; i < max && mismatches.length < 5; i += 1) {
      if (expectedHashes[i] !== actualHashes[i]) {
        mismatches.push({ index: i, expected: expectedHashes[i] || null, actual: actualHashes[i] || null });
      }
    }

    return {
      key: item.key,
      collection: item.collection,
      expectedCount: expectedRows.length,
      actualCount: actualRows.length,
      ok: expectedRows.length === actualRows.length && mismatches.length === 0,
      mismatches
    };
  });
  const failed = rows.filter((row) => !row.ok);
  return {
    ok: failed.length === 0,
    mode: 'mongo-read-backend-compare',
    readGate: mongoReadAdapter.getStatus(),
    totalExpected: rows.reduce((sum, row) => sum + row.expectedCount, 0),
    totalActual: rows.reduce((sum, row) => sum + row.actualCount, 0),
    failedCollections: failed.length,
    rows,
    checkedAt: new Date().toISOString(),
    path: comparePath()
  };
}

function summarizeWindow(samples) {
  const recent = samples.slice(-12);
  let consecutiveOk = 0;
  for (let i = recent.length - 1; i >= 0; i -= 1) {
    if (!recent[i].ok) break;
    consecutiveOk += 1;
  }
  const latest = recent[recent.length - 1] || null;
  return {
    ok: consecutiveOk >= 6 && Boolean(latest && latest.ok),
    sampleCount: recent.length,
    consecutiveOk,
    failedSamples: recent.filter((row) => !row.ok).length,
    latest
  };
}

async function runSample(options = {}) {
  const jsonState = persistentStore.readState();
  const mongo = await mongoReadAdapter.readStateFromMongo({ ignoreGate: true });
  const compare = compareStates(jsonState, mongo.state);
  writeJson(compare.path, compare);

  const previous = readJson(monitorPath(), {
    ok: false,
    mode: 'mongo-read-monitor',
    samples: []
  });
  const sample = {
    ok: compare.ok,
    checkedAt: compare.checkedAt,
    totalExpected: compare.totalExpected,
    totalActual: compare.totalActual,
    failedCollections: compare.failedCollections,
    reason: options.reason || 'manual-monitor-sample'
  };
  const samples = [...(Array.isArray(previous.samples) ? previous.samples : []), sample].slice(-50);
  const window = summarizeWindow(samples);
  const output = {
    ok: window.ok,
    mode: 'mongo-read-monitor',
    latestSample: sample,
    window,
    samples,
    path: monitorPath(),
    updatedAt: new Date().toISOString()
  };
  writeJson(output.path, output);
  return { compare, monitor: output };
}

function getStatus() {
  return readJson(monitorPath(), {
    ok: false,
    mode: 'not-run',
    latestSample: null,
    window: { ok: false, sampleCount: 0, consecutiveOk: 0, failedSamples: 0, latest: null },
    samples: [],
    path: monitorPath()
  });
}

module.exports = {
  compareStates,
  runSample,
  getStatus,
  monitorPath,
  comparePath
};
