import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

const PM2_JLIST_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
const RECENT_INSTABILITY_WINDOW_MS = 60 * 60 * 1000;

export interface Pm2ProcessInfo {
  name: string;
  namespace: string;
  pm_id: number;
  status: string;
  memory: number;
  cpu: number;
  restarts: number;
  uptime: number | null;
  execMode: string;
  instanceTarget: number | null;
}

export interface Pm2AppSummary {
  key: string;
  name: string;
  namespace: string;
  status: 'online' | 'errored' | 'stopped' | 'unknown';
  totalCount: number;
  onlineCount: number;
  erroredCount: number;
  stoppedCount: number;
  unhealthyCount: number;
  memory: number;
  cpu: number;
  restarts: number;
  maxRestarts: number;
  minUptime: number | null;
  execMode: string;
  instanceTarget: number | null;
  pmIds: number[];
  isRecentlyUnstable: boolean;
}

function toFiniteNumber(value: unknown, fallback: number): number {
  const num = typeof value === 'number' ? value : Number(value);
  return Number.isFinite(num) ? num : fallback;
}

function toNullableNumber(value: unknown): number | null {
  const num = typeof value === 'number' ? value : Number(value);
  return Number.isFinite(num) ? num : null;
}

export async function listPm2Processes(): Promise<Pm2ProcessInfo[]> {
  const { stdout } = await execAsync('pm2 jlist', { maxBuffer: PM2_JLIST_MAX_BUFFER_BYTES });
  const parsed = JSON.parse(stdout);
  if (!Array.isArray(parsed)) {
    return [];
  }

  return parsed
    .map((proc: any): Pm2ProcessInfo | null => {
      const name = typeof proc?.name === 'string' ? proc.name : '';
      if (!name) {
        return null;
      }

      return {
        name,
        namespace: typeof proc?.pm2_env?.namespace === 'string' ? proc.pm2_env.namespace : 'default',
        pm_id: toFiniteNumber(proc?.pm_id, -1),
        status: typeof proc?.pm2_env?.status === 'string' ? proc.pm2_env.status : 'unknown',
        memory: toFiniteNumber(proc?.monit?.memory, 0),
        cpu: toFiniteNumber(proc?.monit?.cpu, 0),
        restarts: toFiniteNumber(proc?.pm2_env?.restart_time, 0),
        uptime: toNullableNumber(proc?.pm2_env?.pm_uptime),
        execMode: typeof proc?.pm2_env?.exec_mode === 'string' ? proc.pm2_env.exec_mode : 'unknown',
        instanceTarget: toNullableNumber(proc?.pm2_env?.instances),
      };
    })
    .filter((proc): proc is Pm2ProcessInfo => proc !== null);
}

export function summarizePm2Apps(processes: Pm2ProcessInfo[]): Pm2AppSummary[] {
  const apps = new Map<string, Pm2AppSummary>();

  for (const proc of processes) {
    const key = `${proc.namespace}:${proc.name}`;
    const existing = apps.get(key);
    if (existing) {
      existing.totalCount += 1;
      existing.onlineCount += proc.status === 'online' ? 1 : 0;
      existing.erroredCount += proc.status === 'errored' ? 1 : 0;
      existing.stoppedCount += proc.status === 'stopped' ? 1 : 0;
      existing.unhealthyCount += proc.status === 'online' ? 0 : 1;
      existing.memory += proc.memory;
      existing.cpu += proc.cpu;
      existing.restarts += proc.restarts;
      existing.maxRestarts = Math.max(existing.maxRestarts, proc.restarts);
      existing.minUptime = existing.minUptime == null
        ? proc.uptime
        : proc.uptime == null
          ? existing.minUptime
          : Math.min(existing.minUptime, proc.uptime);
      existing.pmIds.push(proc.pm_id);
      continue;
    }

    apps.set(key, {
      key,
      name: proc.name,
      namespace: proc.namespace,
      status: proc.status === 'online' ? 'online' : proc.status === 'errored' ? 'errored' : proc.status === 'stopped' ? 'stopped' : 'unknown',
      totalCount: 1,
      onlineCount: proc.status === 'online' ? 1 : 0,
      erroredCount: proc.status === 'errored' ? 1 : 0,
      stoppedCount: proc.status === 'stopped' ? 1 : 0,
      unhealthyCount: proc.status === 'online' ? 0 : 1,
      memory: proc.memory,
      cpu: proc.cpu,
      restarts: proc.restarts,
      maxRestarts: proc.restarts,
      minUptime: proc.uptime,
      execMode: proc.execMode,
      instanceTarget: proc.instanceTarget,
      pmIds: [proc.pm_id],
      isRecentlyUnstable: false,
    });
  }

  return Array.from(apps.values())
    .map(app => {
      const hasOnline = app.onlineCount > 0;
      const hasErrored = app.erroredCount > 0;
      const hasStopped = app.stoppedCount > 0;
      const recentlyStarted = app.minUptime != null && (Date.now() - app.minUptime) < RECENT_INSTABILITY_WINDOW_MS;
      app.status = hasOnline ? 'online' : hasErrored ? 'errored' : hasStopped ? 'stopped' : 'unknown';
      app.isRecentlyUnstable = (!hasOnline && (hasErrored || hasStopped)) || (app.maxRestarts >= 5 && recentlyStarted);
      return app;
    })
    .sort((left, right) => left.name.localeCompare(right.name));
}

export function summarizePm2Error(error: unknown): string {
  if (error instanceof Error) {
    return `${error.name}: ${error.message}`;
  }

  return String(error);
}

export function shellQuote(value: string): string {
  return `'${value.replace(/'/g, `'\\''`)}'`;
}
