const fs = require('fs');
const path = require('path');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');
const { isCanonicalBesuQbft } = require('../config/canonicalLedgerConfig');
const evmWalletService = require('./evmWalletService');
const operatorCustodyService = require('./operatorCustodyService');
const { writeUtf8AtomicSync } = require('./atomicFileReplace');

const DATA_PATH = path.join(__dirname, '..', 'data', 'canonical-point-conversions.json');
const LOCK_PATH = `${DATA_PATH}.lock`;
const WORKER_HEARTBEAT_PATH = path.join(__dirname, '..', 'runtime', 'point-conversion-worker.json');
const LOCK_STALE_MS = 2 * 60 * 1000;
const WORKER_FRESH_MS = Math.max(15_000, Number(process.env.PLAIS_POINT_CONVERSION_WORKER_FRESH_MS || 90_000));

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

function conversionError(message, status = 400, code = 'POINT_CONVERSION_ERROR', details = null) {
  const error = new Error(message);
  error.status = status;
  error.code = code;
  if (details) error.details = details;
  return error;
}

function conversionRate() {
  const value = Number(process.env.PLAIS_POINT_CONVERSION_RATE || 1000);
  if (!Number.isSafeInteger(value) || value <= 0) {
    throw conversionError('Point conversion rate is invalid.', 500, 'POINT_CONVERSION_RATE_INVALID');
  }
  return value;
}

function readStore() {
  if (!fs.existsSync(DATA_PATH)) return { version: 1, conversions: [] };
  try {
    const parsed = JSON.parse(fs.readFileSync(DATA_PATH, 'utf8'));
    return { version: 1, conversions: Array.isArray(parsed.conversions) ? parsed.conversions : [] };
  } catch (error) {
    throw conversionError('Canonical point conversion store is invalid.', 500, 'POINT_CONVERSION_STORE_INVALID');
  }
}

function writeStore(store) {
  writeUtf8AtomicSync(DATA_PATH, `${JSON.stringify(store, null, 2)}\n`);
}

function acquireLock() {
  fs.mkdirSync(path.dirname(LOCK_PATH), { recursive: true });
  try {
    return fs.openSync(LOCK_PATH, 'wx');
  } catch (error) {
    if (error.code === 'EEXIST') {
      try {
        const age = Date.now() - fs.statSync(LOCK_PATH).mtimeMs;
        if (age > LOCK_STALE_MS) {
          fs.unlinkSync(LOCK_PATH);
          return fs.openSync(LOCK_PATH, 'wx');
        }
      } catch (_) {}
      throw conversionError('Point conversion store is busy.', 503, 'POINT_CONVERSION_STORE_BUSY');
    }
    throw error;
  }
}

function withStoreLock(mutator) {
  const fd = acquireLock();
  try {
    const store = readStore();
    const result = mutator(store);
    writeStore(store);
    return result;
  } finally {
    try { fs.closeSync(fd); } catch (_) {}
    try { fs.unlinkSync(LOCK_PATH); } catch (_) {}
  }
}

function readWorkerHeartbeat() {
  try {
    const heartbeat = JSON.parse(fs.readFileSync(WORKER_HEARTBEAT_PATH, 'utf8'));
    const ageMs = Date.now() - new Date(heartbeat.observedAt || 0).getTime();
    return {
      online: heartbeat.ready === true && ageMs >= 0 && ageMs <= WORKER_FRESH_MS,
      observedAt: heartbeat.observedAt || null,
      ageMs: Number.isFinite(ageMs) ? ageMs : null
    };
  } catch (_) {
    return { online: false, observedAt: null, ageMs: null };
  }
}

function writeWorkerHeartbeat(ready = true, detail = '') {
  writeUtf8AtomicSync(WORKER_HEARTBEAT_PATH, `${JSON.stringify({
    schemaVersion: 'plais-point-conversion-worker/v1',
    ready: ready === true,
    detail: String(detail || '').slice(0, 200),
    pid: process.pid,
    observedAt: nowIso()
  }, null, 2)}\n`);
}

function getReadiness(userId = '') {
  const ownerId = String(userId || '').trim();
  const recipient = ownerId ? evmWalletService.getWalletByOwner('aisfor_user', ownerId) : null;
  const custody = operatorCustodyService.getStatus();
  const worker = readWorkerHeartbeat();
  const checks = [
    { key: 'requests_enabled', ok: process.env.PLAIS_POINT_CONVERSION_REQUESTS_ENABLED === '1' },
    { key: 'signing_enabled', ok: process.env.PLAIS_POINT_CONVERSION_SIGNING_ENABLED === '1' },
    { key: 'canonical_qbft', ok: isCanonicalBesuQbft() },
    { key: 'evm_enabled', ok: EVM_CANDIDATE_CONFIG.enabled === true },
    { key: 'evm_transfers_enabled', ok: EVM_CANDIDATE_CONFIG.externalTransfersEnabled === true },
    { key: 'operator_custody', ok: custody.ok === true },
    { key: 'conversion_worker', ok: worker.online === true }
  ];
  if (ownerId) checks.push({ key: 'recipient_wallet', ok: Boolean(recipient?.address) });
  return {
    ready: checks.every(check => check.ok),
    rate: conversionRate(),
    mode: 'canonical-besu-qbft',
    chainId: EVM_CANDIDATE_CONFIG.chainId,
    recipientWallet: recipient || null,
    worker,
    checks
  };
}

function normalizeRequest(input = {}) {
  const userId = String(input.userId || '').trim();
  const idempotencyKey = String(input.idempotencyKey || '').trim();
  const pointAmount = Number(input.pointAmount);
  const coinAmount = Number(input.coinAmount);
  const suppliedRate = Number(input.conversionRate);
  const rate = conversionRate();
  if (!userId) throw conversionError('userId is required.', 400, 'POINT_CONVERSION_USER_REQUIRED');
  if (!/^[A-Za-z0-9:_-]{12,220}$/.test(idempotencyKey)) {
    throw conversionError('A stable idempotency key is required.', 400, 'POINT_CONVERSION_IDEMPOTENCY_KEY_INVALID');
  }
  if (!Number.isSafeInteger(pointAmount) || pointAmount < rate) {
    throw conversionError('Point amount must be a positive whole conversion unit.', 400, 'POINT_CONVERSION_AMOUNT_INVALID');
  }
  if (!Number.isSafeInteger(coinAmount) || coinAmount <= 0) {
    throw conversionError('Coin amount must be a positive whole number.', 400, 'POINT_CONVERSION_AMOUNT_INVALID');
  }
  if (suppliedRate !== rate || pointAmount !== coinAmount * rate) {
    throw conversionError(`Exact conversion rate is ${rate} Points = 1 PLAIS.`, 400, 'POINT_CONVERSION_RATE_MISMATCH');
  }
  return { userId, idempotencyKey, pointAmount, coinAmount, rate };
}

function publicConversion(row) {
  if (!row) return null;
  return { ...row };
}

function createConversion(input = {}) {
  const normalized = normalizeRequest(input);
  const readiness = getReadiness(normalized.userId);
  if (!readiness.ready) {
    throw conversionError('Canonical PLAIS point conversion is not ready.', 503, 'POINT_CONVERSION_LOCKED', readiness);
  }
  return withStoreLock((store) => {
    const existing = store.conversions.find(row => row.idempotencyKey === normalized.idempotencyKey);
    if (existing) {
      const same = existing.userId === normalized.userId
        && existing.pointAmount === normalized.pointAmount
        && existing.coinAmount === normalized.coinAmount
        && existing.conversionRate === normalized.rate;
      if (!same) throw conversionError('Idempotency key payload does not match.', 409, 'POINT_CONVERSION_IDEMPOTENCY_CONFLICT');
      return { duplicate: true, conversion: publicConversion(existing) };
    }
    const createdAt = nowIso();
    const row = {
      id: String(input.requestId || normalized.idempotencyKey),
      idempotencyKey: normalized.idempotencyKey,
      userId: normalized.userId,
      pointAmount: normalized.pointAmount,
      coinAmount: normalized.coinAmount,
      conversionRate: normalized.rate,
      recipientAddress: readiness.recipientWallet.address,
      status: 'awaiting_signature',
      chainId: readiness.chainId,
      txHash: '',
      blockNumber: null,
      failureCode: '',
      failureReason: '',
      createdAt,
      updatedAt: createdAt
    };
    store.conversions.push(row);
    return { duplicate: false, conversion: publicConversion(row) };
  });
}

function getConversion(idempotencyKey) {
  const key = String(idempotencyKey || '').trim();
  return publicConversion(readStore().conversions.find(row => row.idempotencyKey === key) || null);
}

function listProcessable(limit = 50) {
  const statuses = new Set(['awaiting_signature', 'submitting', 'submitted']);
  return readStore().conversions
    .filter(row => statuses.has(row.status))
    .slice(0, Math.max(1, Math.min(500, Number(limit) || 50)))
    .map(publicConversion);
}

function updateConversion(idempotencyKey, patch) {
  return withStoreLock((store) => {
    const row = store.conversions.find(item => item.idempotencyKey === idempotencyKey);
    if (!row) throw conversionError('Point conversion was not found.', 404, 'POINT_CONVERSION_NOT_FOUND');
    Object.assign(row, patch, { updatedAt: nowIso() });
    return publicConversion(row);
  });
}

function markSubmitting(key) {
  return updateConversion(key, { status: 'submitting', failureCode: '', failureReason: '' });
}

function markSubmitted(key, txHash) {
  return updateConversion(key, { status: 'submitted', txHash: String(txHash || '') });
}

function markAwaiting(key, error) {
  return updateConversion(key, {
    status: 'awaiting_signature',
    failureCode: String(error?.code || 'POINT_CONVERSION_RETRY_PENDING'),
    failureReason: String(error?.message || error || '').slice(0, 500)
  });
}

function markConfirmed(key, transaction = {}) {
  return updateConversion(key, {
    status: 'confirmed',
    txHash: String(transaction.hash || transaction.txHash || ''),
    blockNumber: Number(transaction.blockNumber || transaction.receipt?.blockNumber || 0) || null,
    confirmedAt: nowIso(),
    failureCode: '',
    failureReason: ''
  });
}

function markFailed(key, error) {
  return updateConversion(key, {
    status: 'failed',
    failureCode: String(error?.code || 'POINT_CONVERSION_FAILED'),
    failureReason: String(error?.message || error || '').slice(0, 500),
    failedAt: nowIso()
  });
}

module.exports = {
  DATA_PATH,
  WORKER_HEARTBEAT_PATH,
  conversionError,
  createConversion,
  getConversion,
  getReadiness,
  listProcessable,
  markAwaiting,
  markConfirmed,
  markFailed,
  markSubmitted,
  markSubmitting,
  normalizeRequest,
  writeWorkerHeartbeat
};
