require('../config/loadEnv')();
const fs = require('fs');
const path = require('path');
const { Wallet, getAddress } = require('ethers');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');
const { isCanonicalBesuQbft } = require('../config/canonicalLedgerConfig');
const conversionService = require('../services/canonicalPointConversionService');
const evmTransferService = require('../services/evmTransferService');
const operatorCustodyService = require('../services/operatorCustodyService');
const { unprotect } = require('../lib/operatorDpapi');

const CONFIRMATION = '--confirm=SIGN_CANONICAL_POINT_CONVERSIONS';
const WATCH = process.argv.includes('--watch');
const WORKER_LOCK_PATH = path.join(__dirname, '..', 'runtime', 'point-conversion-worker.lock');
const INTERVAL_MS = Math.max(5_000, Number(process.env.PLAIS_POINT_CONVERSION_WORKER_INTERVAL_MS || 15_000));
let workerLockFd = null;
let running = false;

function assertActivationGates() {
  const custodyStatus = operatorCustodyService.getStatus();
  const checks = [
    ['explicit confirmation', process.argv.includes(CONFIRMATION)],
    ['conversion requests', process.env.PLAIS_POINT_CONVERSION_REQUESTS_ENABLED === '1'],
    ['conversion signing', process.env.PLAIS_POINT_CONVERSION_SIGNING_ENABLED === '1'],
    ['canonical QBFT', isCanonicalBesuQbft()],
    ['EVM candidate', EVM_CANDIDATE_CONFIG.enabled === true],
    ['EVM transfers', EVM_CANDIDATE_CONFIG.externalTransfersEnabled === true],
    ['operator custody', custodyStatus.ok === true]
  ];
  const failed = checks.filter(([, ok]) => !ok).map(([name]) => name);
  if (failed.length) {
    const custodyFailures = custodyStatus.checks
      .filter(check => !check.ok)
      .map(check => check.key);
    const detail = custodyFailures.length ? ` (${custodyFailures.join(', ')})` : '';
    const error = new Error(`Point conversion worker activation blocked: ${failed.join(', ')}${detail}`);
    error.code = 'POINT_CONVERSION_WORKER_LOCKED';
    throw error;
  }
}

function acquireWorkerLock() {
  fs.mkdirSync(path.dirname(WORKER_LOCK_PATH), { recursive: true });
  try {
    workerLockFd = fs.openSync(WORKER_LOCK_PATH, 'wx');
    fs.writeFileSync(workerLockFd, `${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() })}\n`);
  } catch (error) {
    if (error.code === 'EEXIST') {
      try {
        const ageMs = Date.now() - fs.statSync(WORKER_LOCK_PATH).mtimeMs;
        if (ageMs > Math.max(180_000, INTERVAL_MS * 4)) {
          fs.unlinkSync(WORKER_LOCK_PATH);
          workerLockFd = fs.openSync(WORKER_LOCK_PATH, 'wx');
          fs.writeFileSync(workerLockFd, `${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString(), recoveredStaleLock: true })}\n`);
          return;
        }
      } catch (_) {}
      error.code = 'POINT_CONVERSION_WORKER_ALREADY_RUNNING';
    }
    throw error;
  }
}

function releaseWorkerLock() {
  try { if (workerLockFd !== null) fs.closeSync(workerLockFd); } catch (_) {}
  workerLockFd = null;
  try { fs.unlinkSync(WORKER_LOCK_PATH); } catch (_) {}
}

function loadOperatorPrivateKey() {
  const vaultPath = operatorCustodyService.resolveVaultPath();
  const vault = JSON.parse(fs.readFileSync(vaultPath, 'utf8'));
  const privateKey = unprotect(vault.ciphertext);
  const signer = new Wallet(privateKey);
  const expected = operatorCustodyService.getConfiguredOperatorAddress();
  if (!expected || getAddress(signer.address) !== getAddress(expected)) {
    const error = new Error('Operator vault identity does not match the canonical network.');
    error.code = 'POINT_CONVERSION_OPERATOR_IDENTITY_MISMATCH';
    throw error;
  }
  return privateKey;
}

async function reconcileSubmitted(row) {
  if (!row.txHash) return false;
  const transaction = await evmTransferService.getTransactionDetails(row.txHash);
  if (!transaction || transaction.status === 'pending') return true;
  if (transaction.status === 'confirmed') {
    conversionService.markConfirmed(row.idempotencyKey, transaction);
  } else {
    conversionService.markFailed(row.idempotencyKey, {
      code: 'EVM_TRANSACTION_FAILED',
      message: 'Canonical QBFT transaction receipt failed.'
    });
  }
  return true;
}

async function processOne(row, privateKey) {
  if (row.status === 'submitted' && await reconcileSubmitted(row)) return;
  if (row.status === 'submitting' && row.txHash) {
    await reconcileSubmitted(row);
    return;
  }
  conversionService.markSubmitting(row.idempotencyKey);
  try {
    const transaction = await evmTransferService.sendSignedTransfer({
      privateKey,
      to: row.recipientAddress,
      amount: String(row.coinAmount),
      actorType: 'point_conversion_operator',
      actorId: row.userId,
      memo: `PLAIS point conversion ${row.idempotencyKey}`,
      onBroadcast: broadcast => conversionService.markSubmitted(row.idempotencyKey, broadcast.hash)
    });
    conversionService.markConfirmed(row.idempotencyKey, transaction);
  } catch (error) {
    const latest = conversionService.getConversion(row.idempotencyKey);
    if (latest?.status !== 'submitted' || !latest.txHash) {
      conversionService.markAwaiting(row.idempotencyKey, error);
    }
    throw error;
  }
}

async function processBatch() {
  if (running) return;
  running = true;
  try {
    assertActivationGates();
    conversionService.writeWorkerHeartbeat(true, 'canonical signing worker ready');
    const rows = conversionService.listProcessable(100);
    if (!rows.length) return;
    const privateKey = loadOperatorPrivateKey();
    for (const row of rows) {
      try {
        await processOne(row, privateKey);
        process.stdout.write(`${row.idempotencyKey}: processed\n`);
      } catch (error) {
        process.stderr.write(`${row.idempotencyKey}: ${error.code || 'ERROR'} ${error.message}\n`);
      }
    }
  } finally {
    running = false;
  }
}

async function main() {
  assertActivationGates();
  acquireWorkerLock();
  process.once('exit', releaseWorkerLock);
  process.once('SIGINT', () => { releaseWorkerLock(); process.exit(0); });
  process.once('SIGTERM', () => { releaseWorkerLock(); process.exit(0); });
  await processBatch();
  if (!WATCH) {
    releaseWorkerLock();
    return;
  }
  setInterval(() => {
    processBatch().catch(error => {
      conversionService.writeWorkerHeartbeat(false, error.code || error.message);
      process.stderr.write(`${error.code || 'ERROR'} ${error.message}\n`);
    });
  }, INTERVAL_MS);
}

main().catch(error => {
  try { conversionService.writeWorkerHeartbeat(false, error.code || error.message); } catch (_) {}
  releaseWorkerLock();
  process.stderr.write(`${error.code || 'ERROR'} ${error.message}\n`);
  process.exitCode = 1;
});
