import express from 'express';
import path from 'node:path';
import { Client } from 'discord.js';
import { createClient } from './core/discord.js';
import { addSSEClient, removeSSEClient, sseBroadcast, getSSEClientCount } from './core/sse.js';
import { loadState, getFullState } from './core/persistence.js';
import { getPlugins, registerPlugin, getPluginCtx, type PluginContext } from './core/plugin.js';
import radioPlugin from './plugins/radio/index.js';
import soundboardPlugin from './plugins/soundboard/index.js';

// ── Config ──
const PORT = Number(process.env.PORT ?? 8080);
const DATA_DIR = process.env.DATA_DIR ?? '/data';
const ADMIN_PWD = process.env.ADMIN_PWD ?? '';
const ALLOWED_GUILD_IDS = (process.env.ALLOWED_GUILD_IDS ?? '')
  .split(',').map(s => s.trim()).filter(Boolean);

// Per-bot tokens (DISCORD_TOKEN is legacy fallback for jukebox/soundboard)
const TOKEN_JUKEBOX = process.env.DISCORD_TOKEN_JUKEBOX ?? process.env.DISCORD_TOKEN ?? '';
const TOKEN_RADIO = process.env.DISCORD_TOKEN_RADIO ?? '';

// ── Persistence ──
loadState();

// ── Express ──
const app = express();
app.use(express.json());
app.use(express.static(path.join(import.meta.dirname ?? __dirname, '..', '..', 'web', 'dist')));

// ── Create per-bot clients ──
const clients: { name: string; client: Client; token: string }[] = [];

const clientJukebox = createClient();
if (TOKEN_JUKEBOX) clients.push({ name: 'Jukebox', client: clientJukebox, token: TOKEN_JUKEBOX });

const clientRadio = createClient();
if (TOKEN_RADIO) clients.push({ name: 'Radio', client: clientRadio, token: TOKEN_RADIO });

// ── Plugin Contexts ──
const ctxJukebox: PluginContext = { client: clientJukebox, dataDir: DATA_DIR, adminPwd: ADMIN_PWD, allowedGuildIds: ALLOWED_GUILD_IDS };
const ctxRadio: PluginContext = { client: clientRadio, dataDir: DATA_DIR, adminPwd: ADMIN_PWD, allowedGuildIds: ALLOWED_GUILD_IDS };

// ── SSE Events ──
app.get('/api/events', (_req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  });
  res.flushHeaders();

  // Send snapshot from all plugins
  const snapshot: Record<string, any> = { type: 'snapshot' };
  for (const p of getPlugins()) {
    const pCtx = getPluginCtx(p.name);
    if (p.getSnapshot && pCtx) {
      Object.assign(snapshot, p.getSnapshot(pCtx));
    }
  }
  try { res.write(`data: ${JSON.stringify(snapshot)}\n\n`); } catch {}

  const ping = setInterval(() => { try { res.write(':\n\n'); } catch {} }, 15_000);
  addSSEClient(res);

  _req.on('close', () => {
    removeSSEClient(res);
    clearInterval(ping);
    try { res.end(); } catch {}
  });
});

// ── Health ──
app.get('/api/health', (_req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    plugins: getPlugins().map(p => ({ name: p.name, version: p.version })),
    bots: clients.map(b => ({ name: b.name, user: b.client.user?.tag ?? 'offline', guilds: b.client.guilds.cache.size })),
    sseClients: getSSEClientCount(),
  });
});

// ── Admin Login ──
app.post('/api/admin/login', (req, res) => {
  if (!ADMIN_PWD) { res.status(503).json({ error: 'ADMIN_PWD not configured' }); return; }
  const { password } = req.body ?? {};
  if (password === ADMIN_PWD) {
    res.json({ ok: true });
  } else {
    res.status(401).json({ error: 'Invalid password' });
  }
});

// ── API: List plugins ──
app.get('/api/plugins', (_req, res) => {
  res.json(getPlugins().map(p => ({
    name: p.name,
    version: p.version,
    description: p.description,
  })));
});

// NOTE: SPA fallback is added in boot() AFTER plugin routes

// ── Discord Ready (per bot) ──
function onClientReady(botName: string, client: Client): void {
  client.once('ready', async () => {
    console.log(`[Discord:${botName}] Logged in as ${client.user?.tag} (${client.guilds.cache.size} guilds)`);
    for (const p of getPlugins()) {
      const pCtx = getPluginCtx(p.name);
      if (pCtx?.client === client && p.onReady) {
        try { await p.onReady(pCtx); } catch (e) { console.error(`[Plugin:${p.name}] onReady error:`, e); }
      }
    }
  });
}

// ── Init ──
async function boot(): Promise<void> {
  // ── Register plugins with their bot contexts ──
  registerPlugin(soundboardPlugin, ctxJukebox);
  registerPlugin(radioPlugin, ctxRadio);

  // Init all plugins
  for (const p of getPlugins()) {
    const pCtx = getPluginCtx(p.name)!;
    try {
      await p.init(pCtx);
      p.registerRoutes?.(app, pCtx);
      console.log(`[Plugin:${p.name}] Initialized`);
    } catch (e) {
      console.error(`[Plugin:${p.name}] Init error:`, e);
    }
  }

  // SPA Fallback (MUST be after plugin routes)
  app.get('/{*splat}', (_req, res) => {
    res.sendFile(path.join(import.meta.dirname ?? __dirname, '..', '..', 'web', 'dist', 'index.html'));
  });

  // Start Express
  app.listen(PORT, () => console.log(`[HTTP] Listening on :${PORT}`));

  // Login Discord bots
  for (const bot of clients) {
    onClientReady(bot.name, bot.client);
    try {
      await bot.client.login(bot.token);
    } catch (e) {
      console.error(`[Discord:${bot.name}] Login failed:`, e);
    }
  }

  if (clients.length === 0) {
    console.warn('[Discord] No bot tokens configured - running without Discord');
  }
}

// ── Graceful Shutdown ──
async function shutdown(signal: string): Promise<void> {
  console.log(`\n[${signal}] Shutting down...`);
  for (const p of getPlugins()) {
    const pCtx = getPluginCtx(p.name);
    if (p.destroy && pCtx) {
      try { await p.destroy(pCtx); } catch (e) { console.error(`[Plugin:${p.name}] destroy error:`, e); }
    }
  }
  for (const bot of clients) {
    try { bot.client.destroy(); } catch {}
  }
  process.exit(0);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('uncaughtException', (err) => { console.error('Uncaught:', err); });
process.on('unhandledRejection', (err) => { console.error('Unhandled:', err); });
process.on('warning', (w) => {
  if (w.name === 'TimeoutNegativeWarning') return;
  console.warn(w.name + ': ' + w.message);
});

boot().catch(console.error);
