'use strict';

const assert = require('assert');
const { spawn } = require('child_process');
const net = require('net');
const path = require('path');

const execute = process.argv.includes('--execute');
const services = [
  { id: 'aisfor', root: 'E:\\aisfor', entry: 'server.js', port: 55108 },
  { id: 'aistalk', root: 'F:\\aistalk', entry: 'server.js', port: 55110 },
  { id: 'aisfor-group', root: 'E:\\aisfor-group', entry: 'app.js', port: 55112 },
  { id: 'aismall', root: 'E:\\aismall', entry: 'app.js', port: 55114 },
  { id: 'plai-sharing', root: 'F:\\plai-sharing', entry: 'server.js', port: 55116 }
];

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

function portOpen(port) {
  return new Promise(resolve => {
    const socket = net.createConnection({ host: '127.0.0.1', port });
    const done = value => { socket.destroy(); resolve(value); };
    socket.setTimeout(500);
    socket.once('connect', () => done(true));
    socket.once('timeout', () => done(false));
    socket.once('error', () => done(false));
  });
}

async function waitForHealth(port, child) {
  const deadline = Date.now() + 20_000;
  while (Date.now() < deadline) {
    if (child.exitCode !== null) throw new Error(`PASSIVE_PROCESS_EXITED:${child.exitCode}`);
    try {
      const response = await fetch(`http://127.0.0.1:${port}/__plai/standby-health`, {
        signal: AbortSignal.timeout(1000), redirect: 'error'
      });
      const body = await response.json();
      if (response.status === 200 && body?.ok === true && body?.readOnly === true
        && body?.workersFenced === true && body?.socketsFenced === true) return body;
    } catch (_) {}
    await sleep(200);
  }
  throw new Error('PASSIVE_HEALTH_TIMEOUT');
}

async function stopChild(child, port) {
  if (child.exitCode === null) child.kill();
  const deadline = Date.now() + 10_000;
  while (Date.now() < deadline && await portOpen(port)) await sleep(100);
  if (await portOpen(port)) throw new Error('PASSIVE_PROCESS_PORT_STILL_OPEN');
}

async function runOne(service) {
  if (await portOpen(service.port)) throw new Error(`PASSIVE_CANARY_PORT_IN_USE:${service.port}`);
  const child = spawn(process.execPath, [path.join(service.root, service.entry)], {
    cwd: service.root,
    windowsHide: true,
    env: {
      ...process.env,
      PORT: String(service.port),
      PLAI_SERVICE_MODE: 'PASSIVE_STANDBY',
      PLAI_STANDBY_WRITES_ENABLED: '0',
      PLAI_BACKGROUND_WORKERS_ENABLED: '0',
      PLAI_VALIDATOR_ENABLED: '0',
      PLAI_TRANSACTION_SUBMISSION_ENABLED: '0',
      MONGO_URI: 'mongodb://127.0.0.1:1/passive-canary?serverSelectionTimeoutMS=100',
      REDIS_URL: 'redis://127.0.0.1:1',
      SESSION_SECRET: 'synthetic-passive-canary-session-secret',
      VAPID_PUBLIC_KEY: '',
      VAPID_PRIVATE_KEY: '',
      BACKFILL_DELIVERED_AT: '0'
    },
    stdio: ['ignore', 'pipe', 'pipe']
  });
  let stdoutBytes = 0;
  let stderrBytes = 0;
  child.stdout.on('data', chunk => { stdoutBytes += chunk.length; });
  child.stderr.on('data', chunk => { stderrBytes += chunk.length; });
  try {
    const health = await waitForHealth(service.port, child);
    assert.strictEqual(health.serviceId, service.id);
    const dynamicResponse = await fetch(`http://127.0.0.1:${service.port}/dynamic-canary`, {
      signal: AbortSignal.timeout(2000), redirect: 'error'
    });
    const dynamicBody = await dynamicResponse.json();
    assert.strictEqual(dynamicResponse.status, 503);
    assert.strictEqual(dynamicBody.code, 'PASSIVE_STANDBY_DYNAMIC_REQUEST_FENCED');
    const writeResponse = await fetch(`http://127.0.0.1:${service.port}/write-canary`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
      signal: AbortSignal.timeout(2000), redirect: 'error'
    });
    const writeBody = await writeResponse.json();
    assert.strictEqual(writeResponse.status, 503);
    assert.strictEqual(writeBody.code, 'PASSIVE_STANDBY_WRITE_FENCED');
    return {
      serviceId: service.id,
      port: service.port,
      healthStatus: 200,
      dynamicRequestStatus: 503,
      writeRequestStatus: 503,
      readOnly: true,
      workersFenced: true,
      socketsFenced: true,
      stdoutBytesCapturedNotPrinted: stdoutBytes,
      stderrBytesCapturedNotPrinted: stderrBytes
    };
  } finally {
    await stopChild(child, service.port);
  }
}

(async () => {
  process.stdout.write(`${JSON.stringify({
    ok: true,
    mode: execute ? 'EXECUTE_LOOPBACK_PASSIVE_FLEET_CANARY' : 'PLAN_ONLY_NO_PROCESSES',
    serviceCount: services.length,
    ports: services.map(row => row.port),
    productionDatabaseTargeted: false,
    productionRedisTargeted: false,
    productionTrafficChanged: false,
    dnsChanged: false,
    externalTransfersMade: false
  }, null, 2)}\n`);
  if (!execute) return;
  const results = [];
  for (const service of services) results.push(await runOne(service));
  process.stdout.write(`${JSON.stringify({
    ok: true,
    status: 'FIVE_SERVICE_LOOPBACK_PASSIVE_STANDBY_PROCESSES_PROVEN_AND_STOPPED',
    results,
    allProcessesStopped: true,
    allPortsClosed: true,
    productionChanged: false
  }, null, 2)}\n`);
})().catch(error => {
  console.error(JSON.stringify({ ok: false, error: String(error.message || error) }, null, 2));
  process.exitCode = 1;
});
