import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { resolveDaemonPaths } from '../src/daemon/config.ts';
import {
  isAgentDeviceDaemonProcess,
  stopProcessForTakeover,
} from '../src/daemon/daemon-process.ts';
import { cleanupRunnerLeasesForOwner } from '../src/platforms/apple/core/runner/runner-lease.ts';
import { runnerLeaseCleanupAdapter } from '../src/platforms/apple/core/runner/runner-disposal.ts';

const DAEMON_TERM_TIMEOUT_MS = 15_000;
const DAEMON_KILL_TIMEOUT_MS = 2_000;
const PRUNE_DEV_FLAG = '--prune-dev';
const PRUNE_DEV_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;

type DaemonInfo = {
  pid?: number;
  processStartTime?: string;
};

const paths = resolveDaemonPaths(process.env.AGENT_DEVICE_STATE_DIR);
const info = readDaemonInfo(paths.infoPath);
const daemonPid = readPositivePid(info?.pid);

if (daemonPid !== null) {
  await stopProcessForTakeover(daemonPid, {
    termTimeoutMs: DAEMON_TERM_TIMEOUT_MS,
    killTimeoutMs: DAEMON_KILL_TIMEOUT_MS,
    expectedStartTime: info?.processStartTime,
  });
  await cleanupRunnerLeasesForOwner(
    { pid: daemonPid, startTime: info?.processStartTime },
    runnerLeaseCleanupAdapter,
  );
}

removeIfPresent(paths.infoPath);
removeIfPresent(paths.lockPath);

if (process.argv.includes(PRUNE_DEV_FLAG)) {
  pruneStaleDevStateDirs();
}

function readDaemonInfo(infoPath: string): DaemonInfo | null {
  try {
    return JSON.parse(fs.readFileSync(infoPath, 'utf8')) as DaemonInfo;
  } catch {
    return null;
  }
}

function removeIfPresent(filePath: string): void {
  try {
    fs.unlinkSync(filePath);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
      throw error;
    }
  }
}

// Removes worktree-scoped state dirs under ~/.agent-device/dev/ that no live daemon
// owns and that have been idle past the retention threshold. Never touches the
// global ~/.agent-device root contents.
function pruneStaleDevStateDirs(): void {
  const devRoot = path.join(os.homedir(), '.agent-device', 'dev');
  const cutoffMs = Date.now() - PRUNE_DEV_MAX_AGE_MS;
  for (const dirPath of listDevStateDirs(devRoot)) {
    if (hasLiveDaemon(dirPath) || newestMtimeMs(dirPath) > cutoffMs) continue;
    fs.rmSync(dirPath, { recursive: true, force: true });
    process.stdout.write(`Removed stale daemon state dir: ${dirPath}\n`);
  }
}

function listDevStateDirs(devRoot: string): string[] {
  let entries: fs.Dirent[];
  try {
    entries = fs.readdirSync(devRoot, { withFileTypes: true });
  } catch {
    return [];
  }
  return entries
    .filter((entry) => entry.isDirectory())
    .map((entry) => path.join(devRoot, entry.name));
}

function hasLiveDaemon(stateDir: string): boolean {
  const dirInfo = readDaemonInfo(path.join(stateDir, 'daemon.json'));
  const pid = readPositivePid(dirInfo?.pid);
  return pid !== null && isAgentDeviceDaemonProcess(pid, dirInfo?.processStartTime);
}

function readPositivePid(pid: number | undefined): number | null {
  if (typeof pid !== 'number') return null;
  return Number.isInteger(pid) && pid > 0 ? pid : null;
}

function newestMtimeMs(dirPath: string): number {
  let newest = statMtimeMs(dirPath);
  let children: fs.Dirent[];
  try {
    children = fs.readdirSync(dirPath, { withFileTypes: true });
  } catch {
    return newest;
  }
  for (const child of children) {
    newest = Math.max(newest, statMtimeMs(path.join(dirPath, child.name)));
  }
  return newest;
}

function statMtimeMs(filePath: string): number {
  try {
    return fs.statSync(filePath).mtimeMs;
  } catch {
    return 0;
  }
}
