import net from 'node:net';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { mkdirSync, statSync, unwatchFile, watchFile, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

const HOST = process.env.AUTOMATION_DEV_HOST ?? '127.0.0.1';
const PORT = Number(process.env.AUTOMATION_PORT ?? '3030');
const TSX_BIN = process.platform === 'win32' ? 'tsx.cmd' : 'tsx';
const SERVER_ARGS = ['watch', 'automation/internal/server.ts'];
const RESTART_DELAY_MS = 800;
const RESTART_WINDOW_MS = 15_000;
const MAX_RESTARTS = 3;
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const AUTOMATION_CONTROL_DIR = path.join(PROJECT_ROOT, '.runtime', 'automation-control');
const AUTOMATION_RESTART_SIGNAL_FILE = path.join(AUTOMATION_CONTROL_DIR, `restart-${PORT}.signal`);
const AUTOMATION_SUPERVISOR_STATE_FILE = path.join(AUTOMATION_CONTROL_DIR, `state-${PORT}.json`);

function isPortOccupied(port, host) {
  return new Promise((resolve) => {
    const socket = net.createConnection({ port, host });

    const finish = (occupied) => {
      socket.removeAllListeners();
      socket.destroy();
      resolve(occupied);
    };

    socket.once('connect', () => finish(true));
    socket.once('timeout', () => finish(false));
    socket.once('error', (error) => {
      if (error && (error.code === 'ECONNREFUSED' || error.code === 'EHOSTUNREACH')) {
        finish(false);
        return;
      }

      finish(true);
    });
    socket.setTimeout(500);
  });
}

function writeSupervisorState(status, extra = {}) {
  mkdirSync(AUTOMATION_CONTROL_DIR, { recursive: true });
  writeFileSync(AUTOMATION_SUPERVISOR_STATE_FILE, JSON.stringify({
    port: PORT,
    status,
    supervisorPid: process.pid,
    updatedAt: new Date().toISOString(),
    ...extra,
  }, null, 2));
}

function waitForShutdown(reason = 'port already occupied by another process') {
  console.log(`[api] Port ${PORT} is already in use; assuming the automation API is already running and skipping duplicate startup.`);
  writeSupervisorState('unmanaged', { reason });

  const interval = setInterval(() => {}, 60_000);
  const shutdown = () => {
    clearInterval(interval);
    writeSupervisorState('stopped', { reason: 'supervisor shutdown while port was unmanaged' });
    process.exit(0);
  };

  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);
}

function ensureRestartSignalFile() {
  mkdirSync(AUTOMATION_CONTROL_DIR, { recursive: true });
  writeFileSync(AUTOMATION_RESTART_SIGNAL_FILE, '', { flag: 'a' });

  try {
    return statSync(AUTOMATION_RESTART_SIGNAL_FILE).mtimeMs;
  } catch {
    return 0;
  }
}

async function main() {
  let lastRestartSignalMtimeMs = ensureRestartSignalFile();
  const occupied = await isPortOccupied(PORT, HOST);
  if (occupied) {
    waitForShutdown();
    return;
  }

  let child;
  let shuttingDown = false;
  let restartCount = 0;
  let lastStartedAt = 0;
  let manualRestartRequested = false;

  const stopWatchingRestartSignal = () => {
    unwatchFile(AUTOMATION_RESTART_SIGNAL_FILE);
  };

  const forwardSignal = (signal) => {
    shuttingDown = true;
    stopWatchingRestartSignal();
    writeSupervisorState('stopping', {
      childPid: child?.pid,
      reason: `received ${signal}`,
    });
    if (child && !child.killed && child.exitCode === null) {
      child.kill(signal);
      return;
    }

    writeSupervisorState('stopped', { reason: `received ${signal}` });
    process.exit(0);
  };

  const requestManualRestart = (reason) => {
    if (shuttingDown) {
      return;
    }

    console.warn(`[api] 收到自动化服务重启信号: ${reason}`);
    restartCount = 0;

    if (child && !child.killed && child.exitCode === null) {
      if (manualRestartRequested) {
        return;
      }

      manualRestartRequested = true;
      writeSupervisorState('stopping', {
        childPid: child.pid,
        reason,
      });
      const killed = child.kill('SIGTERM');
      if (!killed) {
        manualRestartRequested = false;
        startChild();
      }
      return;
    }

    manualRestartRequested = false;
    startChild();
  };

  const startChild = () => {
    lastStartedAt = Date.now();
    child = spawn(TSX_BIN, SERVER_ARGS, {
      cwd: PROJECT_ROOT,
      env: process.env,
      stdio: 'inherit',
    });
    writeSupervisorState('starting', {
      childPid: child.pid,
      reason: manualRestartRequested ? 'manual restart requested' : 'supervisor start',
    });

    child.on('exit', async (code, signal) => {
      if (shuttingDown) {
        stopWatchingRestartSignal();
        writeSupervisorState('stopped', {
          childPid: child?.pid,
          reason: `child exited during shutdown (code=${code ?? 'null'}, signal=${signal ?? 'null'})`,
        });
        process.exit(code ?? 0);
        return;
      }

      if (manualRestartRequested) {
        manualRestartRequested = false;
        console.warn('[api] 正在根据重启信号重新拉起 automation API...');
        writeSupervisorState('starting', {
          childPid: child?.pid,
          reason: 'manual restart child exited; relaunch pending',
        });
        setTimeout(startChild, RESTART_DELAY_MS);
        return;
      }

      const uptime = Date.now() - lastStartedAt;
      if (uptime > RESTART_WINDOW_MS) {
        restartCount = 0;
      }
      restartCount += 1;

      const portOccupied = await isPortOccupied(PORT, HOST);
      if (portOccupied) {
        waitForShutdown('port became occupied by an unmanaged process after child exit');
        return;
      }

      if (restartCount > MAX_RESTARTS) {
        console.error(`[api] Automation API exited unexpectedly with code ${code ?? 'null'} after ${Math.round(uptime / 1000)}s; giving up after ${MAX_RESTARTS} restarts.`);
        stopWatchingRestartSignal();
        writeSupervisorState('stopped', {
          childPid: child?.pid,
          reason: `child exited repeatedly (code=${code ?? 'null'}, signal=${signal ?? 'null'})`,
        });
        process.exit(code ?? 1);
        return;
      }

      console.warn(`[api] Automation API exited unexpectedly with code ${code ?? 'null'} after ${Math.round(uptime / 1000)}s; restarting (${restartCount}/${MAX_RESTARTS})...`);
      setTimeout(startChild, RESTART_DELAY_MS);
    });
  };

  watchFile(AUTOMATION_RESTART_SIGNAL_FILE, { interval: 300 }, (curr) => {
    if (curr.mtimeMs === 0 || curr.mtimeMs <= lastRestartSignalMtimeMs) {
      return;
    }

    lastRestartSignalMtimeMs = curr.mtimeMs;
    requestManualRestart('检测到本地重启信号文件更新');
  });

  process.on('SIGINT', () => forwardSignal('SIGINT'));
  process.on('SIGTERM', () => forwardSignal('SIGTERM'));

  startChild();
}

main().catch((error) => {
  unwatchFile(AUTOMATION_RESTART_SIGNAL_FILE);
  console.error('[api] Failed to prepare automation API:', error);
  process.exit(1);
});
