import 'dotenv/config';
import express from 'express';
import { createServer } from 'node:http';
import { execFile } from 'node:child_process';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { join, resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { tmpdir } from 'node:os';
import authRouter from './auth.js';
import projectsRouter from './projects.js';
import teacherRouter from './teacher.js';
import { bootstrap, purgeExpiredSessions } from './db.js';
import { attachWsServer } from './ws.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 3001;

app.use(express.json({ limit: '2mb' }));

// ─── Auth ────────────────────────────────────────────────

app.use('/api/auth', authRouter);
app.use('/api/projects', projectsRouter);
app.use('/api/teacher', teacherRouter);

// ─── Helpers (Arduino CLI) ───────────────────────────────

const CLI = process.env.ARDUINO_CLI || 'arduino-cli';

function run(args, timeout = 120_000) {
  return new Promise((resolve, reject) => {
    execFile(CLI, args, { timeout }, (err, stdout, stderr) => {
      if (err) {
        const msg = stderr?.trim() || stdout?.trim() || err.message;
        reject(new Error(msg));
      } else {
        resolve({ stdout, stderr });
      }
    });
  });
}

async function writeSketchDir(code) {
  const dir = await mkdtemp(join(tmpdir(), 'arduino-sketch-'));
  const sketchDir = join(dir, 'sketch');
  const { mkdir } = await import('node:fs/promises');
  await mkdir(sketchDir, { recursive: true });
  await writeFile(join(sketchDir, 'sketch.ino'), code, 'utf-8');
  return { dir, sketchDir };
}

async function cleanupDir(dir) {
  try {
    await rm(dir, { recursive: true, force: true });
  } catch {
    // best-effort cleanup
  }
}

// ─── Routes (Arduino CLI) ────────────────────────────────

app.get('/api/arduino/status', async (_req, res) => {
  try {
    const { stdout } = await run(['version', '--format', 'json']);
    const info = JSON.parse(stdout);
    res.json({ ok: true, version: info.VersionString || info.version || stdout.trim() });
  } catch (err) {
    res.status(503).json({ ok: false, error: `arduino-cli not available: ${err.message}` });
  }
});

app.get('/api/arduino/boards', async (_req, res) => {
  try {
    const { stdout } = await run(['board', 'list', '--format', 'json']);
    const raw = JSON.parse(stdout);

    const ports = Array.isArray(raw) ? raw : (raw.detected_ports || []);

    const boards = ports
      .filter(p => p.port)
      .map(entry => {
        const port = entry.port?.address || entry.port?.label || entry.address || '';
        const matchingBoards = entry.matching_boards || entry.boards || [];
        const first = matchingBoards[0] || {};
        return {
          port,
          name: first.name || entry.port?.protocol_label || 'Unknown',
          fqbn: first.fqbn || '',
        };
      })
      .filter(b => b.port);

    res.json(boards);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.post('/api/arduino/compile', async (req, res) => {
  const { code } = req.body;
  if (!code) return res.status(400).json({ error: 'No code provided' });

  let dir;
  try {
    ({ dir } = await writeSketchDir(code));
    const sketchDir = join(dir, 'sketch');
    const { stdout, stderr } = await run([
      'compile',
      '--fqbn', req.body.fqbn || 'arduino:avr:uno',
      sketchDir,
    ]);
    res.json({ ok: true, output: stdout + stderr });
  } catch (err) {
    res.status(400).json({ ok: false, error: err.message });
  } finally {
    if (dir) cleanupDir(dir);
  }
});

app.post('/api/arduino/upload', async (req, res) => {
  const { code, port, fqbn } = req.body;
  if (!code) return res.status(400).json({ error: 'No code provided' });
  if (!port) return res.status(400).json({ error: 'No port specified' });

  let dir;
  try {
    ({ dir } = await writeSketchDir(code));
    const sketchDir = join(dir, 'sketch');
    const { stdout, stderr } = await run([
      'compile',
      '--upload',
      '--fqbn', fqbn || 'arduino:avr:uno',
      '--port', port,
      sketchDir,
    ]);
    res.json({ ok: true, output: stdout + stderr });
  } catch (err) {
    res.status(400).json({ ok: false, error: err.message });
  } finally {
    if (dir) cleanupDir(dir);
  }
});

// ─── Static (built frontend) ─────────────────────────────
// Serve the Vite build output. In dev, the Vite server handles assets and
// proxies /api + /ws to this server instead (see vite.config.js).

const DIST_DIR = resolve(__dirname, '..', 'dist');
app.use(express.static(DIST_DIR));

// ─── Start ───────────────────────────────────────────────

const server = createServer(app);
attachWsServer(server);

(async () => {
  try {
    await bootstrap();
    purgeExpiredSessions().catch(() => { /* ignore */ });
    setInterval(() => purgeExpiredSessions().catch(() => {}), 60 * 60 * 1000);
  } catch (err) {
    console.error('Database bootstrap failed:', err.message);
    console.error('Auth + realtime features will be unavailable. Check MYSQL_* env vars.');
  }

  server.listen(PORT, () => {
    console.log(`esp32block server listening on http://localhost:${PORT}`);
    console.log(`  static: ${DIST_DIR}`);
    console.log(`  arduino CLI: ${CLI}`);
  });
})();
