import { spawn, execSync } from 'node:child_process';
import {
  existsSync,
  mkdirSync,
  readFileSync,
  writeFileSync,
  unlinkSync,
  openSync,
  constants,
} from 'node:fs';
import { join, resolve } from 'node:path';
import { homedir } from 'node:os';
import { createRequire } from 'node:module';

// ─── Paths ──────────────────────────────────────────────────────────────────

export const GATEWAY_HOME = resolve(
  process.env['MOSAIC_GATEWAY_HOME'] ?? join(homedir(), '.config', 'mosaic', 'gateway'),
);
export const PID_FILE = join(GATEWAY_HOME, 'daemon.pid');
export const LOG_DIR = join(GATEWAY_HOME, 'logs');
export const LOG_FILE = join(LOG_DIR, 'gateway.log');
export const ENV_FILE = join(GATEWAY_HOME, '.env');
export const META_FILE = join(GATEWAY_HOME, 'meta.json');

// ─── Meta ───────────────────────────────────────────────────────────────────

export interface GatewayMeta {
  version: string;
  installedAt: string;
  entryPoint: string;
  adminToken?: string;
  host: string;
  port: number;
}

export function readMeta(): GatewayMeta | null {
  if (!existsSync(META_FILE)) return null;
  try {
    return JSON.parse(readFileSync(META_FILE, 'utf-8')) as GatewayMeta;
  } catch {
    return null;
  }
}

export function writeMeta(meta: GatewayMeta): void {
  ensureDirs();
  writeFileSync(META_FILE, JSON.stringify(meta, null, 2), { mode: 0o600 });
}

// ─── Directories ────────────────────────────────────────────────────────────

export function ensureDirs(): void {
  mkdirSync(GATEWAY_HOME, { recursive: true, mode: 0o700 });
  mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
}

// ─── PID management ─────────────────────────────────────────────────────────

export function readPid(): number | null {
  if (!existsSync(PID_FILE)) return null;
  try {
    const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
    return Number.isNaN(pid) ? null : pid;
  } catch {
    return null;
  }
}

export function isRunning(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}

export function getDaemonPid(): number | null {
  const pid = readPid();
  if (pid === null) return null;
  return isRunning(pid) ? pid : null;
}

// ─── Entry point resolution ─────────────────────────────────────────────────

export function resolveGatewayEntry(): string {
  // Check meta.json for custom entry point
  const meta = readMeta();
  if (meta?.entryPoint && existsSync(meta.entryPoint)) {
    return meta.entryPoint;
  }

  // Try to resolve from globally installed @mosaicstack/gateway
  try {
    const req = createRequire(import.meta.url);
    const pkgPath = req.resolve('@mosaicstack/gateway/package.json');
    const mainEntry = join(resolve(pkgPath, '..'), 'dist', 'main.js');
    if (existsSync(mainEntry)) return mainEntry;
  } catch {
    // Not installed globally
  }

  throw new Error('Cannot find gateway entry point. Run `mosaic gateway install` first.');
}

// ─── Start / Stop / Health ──────────────────────────────────────────────────

export function startDaemon(): number {
  const running = getDaemonPid();
  if (running !== null) {
    throw new Error(`Gateway is already running (PID ${running.toString()})`);
  }

  ensureDirs();
  const entryPoint = resolveGatewayEntry();

  // Load env vars from gateway .env
  const env: Record<string, string> = { ...process.env } as Record<string, string>;
  if (existsSync(ENV_FILE)) {
    for (const line of readFileSync(ENV_FILE, 'utf-8').split('\n')) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;
      const eqIdx = trimmed.indexOf('=');
      if (eqIdx > 0) env[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
    }
  }

  const logFd = openSync(LOG_FILE, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND);

  const child = spawn('node', [entryPoint], {
    detached: true,
    stdio: ['ignore', logFd, logFd],
    env,
    cwd: GATEWAY_HOME,
  });

  if (!child.pid) {
    throw new Error('Failed to spawn gateway process');
  }

  writeFileSync(PID_FILE, child.pid.toString(), { mode: 0o600 });
  child.unref();

  return child.pid;
}

export async function stopDaemon(timeoutMs = 10_000): Promise<void> {
  const pid = getDaemonPid();
  if (pid === null) {
    throw new Error('Gateway is not running');
  }

  process.kill(pid, 'SIGTERM');

  // Poll for exit
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    if (!isRunning(pid)) {
      cleanPidFile();
      return;
    }
    await sleep(250);
  }

  // Force kill
  try {
    process.kill(pid, 'SIGKILL');
  } catch {
    // Already dead
  }
  cleanPidFile();
}

function cleanPidFile(): void {
  try {
    unlinkSync(PID_FILE);
  } catch {
    // Ignore
  }
}

export async function waitForHealth(
  host: string,
  port: number,
  timeoutMs = 30_000,
): Promise<boolean> {
  const start = Date.now();
  let delay = 500;

  while (Date.now() - start < timeoutMs) {
    try {
      const res = await fetch(`http://${host}:${port.toString()}/health`);
      if (res.ok) return true;
    } catch {
      // Not ready yet
    }
    await sleep(delay);
    delay = Math.min(delay * 1.5, 3000);
  }
  return false;
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// ─── npm install helper ─────────────────────────────────────────────────────

const GITEA_REGISTRY = 'https://git.mosaicstack.dev/api/packages/mosaic/npm/';

export function installGatewayPackage(): void {
  console.log('Installing @mosaicstack/gateway from Gitea registry...');
  execSync(`npm install -g @mosaicstack/gateway@latest --@mosaic:registry=${GITEA_REGISTRY}`, {
    stdio: 'inherit',
    timeout: 120_000,
  });
}

export function uninstallGatewayPackage(): void {
  try {
    execSync('npm uninstall -g @mosaicstack/gateway', {
      stdio: 'inherit',
      timeout: 60_000,
    });
  } catch {
    console.warn('Warning: npm uninstall may not have completed cleanly.');
  }
}

export function getInstalledGatewayVersion(): string | null {
  try {
    const output = execSync('npm ls -g @mosaicstack/gateway --json --depth=0', {
      encoding: 'utf-8',
      timeout: 15_000,
      stdio: ['pipe', 'pipe', 'pipe'],
    });
    const data = JSON.parse(output) as {
      dependencies?: { '@mosaicstack/gateway'?: { version?: string } };
    };
    return data.dependencies?.['@mosaicstack/gateway']?.version ?? null;
  } catch {
    return null;
  }
}
