import { createRouter } from '../../create-router';

type AppSpec = {
  id: string;
  name: string;
  description: string;
  versionCommand: string[];
  versionParser: (output: string) => string | null;
  installCommand?: string[];
  updateCommand?: string[];
  manualInstallCommand?: string;
  manualUpdateCommand?: string;
  processName?: string;
};

type AppStatus = {
  id: string;
  name: string;
  description: string;
  installed: boolean;
  version: string | null;
  running: boolean | null;
  hasInstall: boolean;
  hasUpdate: boolean;
  manualInstallCommand: string | null;
  manualUpdateCommand: string | null;
};

const detectPackageManager = (): 'pacman' | 'apt' | 'brew' | null => {
  for (const [cmd, name] of [
    ['pacman', 'pacman'],
    ['apt', 'apt'],
    ['brew', 'brew'],
  ] as const) {
    try {
      const proc = Bun.spawnSync(['which', cmd], { stdout: 'pipe', stderr: 'pipe' });
      if (proc.exitCode === 0) return name;
    } catch {
      // continue
    }
  }
  return null;
};

type FfmpegCommands = {
  installCommand?: string[];
  updateCommand?: string[];
  manualInstallCommand?: string;
  manualUpdateCommand?: string;
};

const getFfmpegCommands = (): FfmpegCommands => {
  const pm = detectPackageManager();
  if (pm === 'pacman')
    return {
      manualInstallCommand: 'sudo pacman -S ffmpeg',
      manualUpdateCommand: 'sudo pacman -Syu ffmpeg',
    };
  if (pm === 'apt')
    return {
      manualInstallCommand: 'sudo apt install ffmpeg',
      manualUpdateCommand: 'sudo apt install --only-upgrade ffmpeg',
    };
  if (pm === 'brew')
    return {
      installCommand: ['brew', 'install', 'ffmpeg'],
      updateCommand: ['brew', 'upgrade', 'ffmpeg'],
    };
  return {};
};

const ffmpegCmds = getFfmpegCommands();

const apps: AppSpec[] = [
  {
    id: 'ffmpeg',
    name: 'FFmpeg',
    description: 'Audio and video processing toolkit',
    versionCommand: ['ffmpeg', '-version'],
    versionParser: (output) => {
      const match = output.match(/ffmpeg version (\S+)/);
      return match?.[1] ?? null;
    },
    installCommand: ffmpegCmds.installCommand,
    updateCommand: ffmpegCmds.updateCommand,
    manualInstallCommand: ffmpegCmds.manualInstallCommand,
    manualUpdateCommand: ffmpegCmds.manualUpdateCommand,
  },
  {
    id: 'sharp',
    name: 'Sharp',
    description: 'High-performance image processing library',
    versionCommand: ['bun', '--eval', "console.log(require('sharp').versions.sharp)"],
    versionParser: (output) => output.trim() || null,
    installCommand: ['bun', 'add', 'sharp'],
    updateCommand: ['bun', 'add', 'sharp@latest'],
  },
  {
    id: 'whisper-cpp',
    name: 'Whisper.cpp',
    description: 'Speech-to-text inference engine',
    versionCommand: ['whisper-cpp', '--version'],
    versionParser: (output) => output.trim() || null,
    installCommand: detectPackageManager() === 'brew' ? ['brew', 'install', 'whisper-cpp'] : undefined,
    processName: 'whisper-server',
  },
  {
    id: 'mlx-audio',
    name: 'MLX Audio',
    description: 'Audio processing with Apple MLX framework',
    versionCommand: ['pip', 'show', 'mlx-audio'],
    versionParser: (output) => {
      const match = output.match(/Version:\s*(\S+)/);
      return match?.[1] ?? null;
    },
    installCommand: ['pip', 'install', 'mlx-audio'],
    updateCommand: ['pip', 'install', '--upgrade', 'mlx-audio'],
  },
];

const COMMAND_TIMEOUT_MS = 30_000;

const runCommand = async (command: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> => {
  try {
    const proc = Bun.spawn(command, { stdout: 'pipe', stderr: 'pipe' });
    const timeout = new Promise<never>((_, reject) =>
      setTimeout(() => {
        proc.kill();
        reject(new Error('Command timed out'));
      }, COMMAND_TIMEOUT_MS),
    );
    const result = Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
    const [stdout, stderr] = await Promise.race([result, timeout]);
    return { exitCode: proc.exitCode ?? 1, stdout, stderr };
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Command not found or failed to execute';
    return { exitCode: 1, stdout: '', stderr: message };
  }
};

const checkVersion = async (app: AppSpec): Promise<string | null> => {
  const result = await runCommand(app.versionCommand);
  if (result.exitCode !== 0) return null;
  return app.versionParser(result.stdout);
};

const checkRunning = async (processName: string): Promise<boolean> => {
  const result = await runCommand(['pgrep', '-x', processName]);
  return result.exitCode === 0;
};

const getAppStatus = async (app: AppSpec): Promise<AppStatus> => {
  const version = await checkVersion(app);
  const running = app.processName ? await checkRunning(app.processName) : null;
  return {
    id: app.id,
    name: app.name,
    description: app.description,
    installed: version !== null,
    version,
    running,
    hasInstall: !!(app.installCommand || app.manualInstallCommand),
    hasUpdate: !!(app.updateCommand ?? app.installCommand ?? app.manualUpdateCommand ?? app.manualInstallCommand),
    manualInstallCommand: app.manualInstallCommand ?? null,
    manualUpdateCommand: app.manualUpdateCommand ?? null,
  };
};

export const applicationsRouter = createRouter();

applicationsRouter.get('/', async (ctx) => {
  const statuses = await Promise.all(apps.map(getAppStatus));
  return ctx.json(statuses);
});

applicationsRouter.get('/:id', async (ctx) => {
  const app = apps.find((a) => a.id === ctx.req.param('id'));
  if (!app) return ctx.json({ error: 'Application not found' }, 404);
  const status = await getAppStatus(app);
  return ctx.json(status);
});

applicationsRouter.post('/:id/install', async (ctx) => {
  const app = apps.find((a) => a.id === ctx.req.param('id'));
  if (!app) return ctx.json({ error: 'Application not found' }, 404);
  if (!app.installCommand) return ctx.json({ error: 'No install command available for this platform' }, 400);

  const result = await runCommand(app.installCommand);
  if (result.exitCode !== 0) {
    return ctx.json({ error: result.stderr.trim() || 'Installation failed' }, 500);
  }

  const status = await getAppStatus(app);
  return ctx.json(status);
});

applicationsRouter.post('/:id/update', async (ctx) => {
  const app = apps.find((a) => a.id === ctx.req.param('id'));
  if (!app) return ctx.json({ error: 'Application not found' }, 404);

  const command = app.updateCommand ?? app.installCommand;
  if (!command) return ctx.json({ error: 'No update command available for this platform' }, 400);

  const result = await runCommand(command);
  if (result.exitCode !== 0) {
    return ctx.json({ error: result.stderr.trim() || 'Update failed' }, 500);
  }

  const status = await getAppStatus(app);
  return ctx.json(status);
});
