import { Router } from 'express';
import pool from '../db';
import { execSync } from 'child_process';
import { readFileSync, writeFileSync, copyFileSync, existsSync } from 'fs';

const router = Router();

// Cache PM2 status for 5 seconds
let pm2Cache: { data: any[]; ts: number } = { data: [], ts: 0 };

function getPm2Status(): any[] {
  const now = Date.now();
  if (now - pm2Cache.ts < 5000 && pm2Cache.data.length > 0) {
    return pm2Cache.data;
  }
  try {
    const raw = execSync('pm2 jlist', { timeout: 10000, encoding: 'utf-8' });
    const procs = JSON.parse(raw);
    pm2Cache = { data: procs, ts: now };
    return procs;
  } catch {
    return pm2Cache.data;
  }
}

function findPm2Process(name: string) {
  const procs = getPm2Status();
  return procs.find((p: any) => p.name === name) || null;
}

function formatUptime(ms: number): string {
  const secs = Math.floor(ms / 1000);
  const days = Math.floor(secs / 86400);
  const hours = Math.floor((secs % 86400) / 3600);
  const mins = Math.floor((secs % 3600) / 60);
  if (days > 0) return `${days}d ${hours}h`;
  if (hours > 0) return `${hours}h ${mins}m`;
  return `${mins}m`;
}

function getLogPaths(proc: any): { outLog: string; errLog: string; combinedLog: string } {
  if (!proc) return { outLog: '', errLog: '', combinedLog: '' };
  const env = proc.pm2_env || {};
  return {
    outLog: env.pm_out_log_path || '',
    errLog: env.pm_err_log_path || '',
    combinedLog: env.pm_log_path || '',
  };
}

function tailFile(filePath: string, lines: number): string {
  if (!filePath || !existsSync(filePath)) return '';
  try {
    return execSync(`tail -n ${lines} ${JSON.stringify(filePath)}`, { timeout: 5000, encoding: 'utf-8' });
  } catch {
    return '';
  }
}

function getLastLogLine(proc: any): string {
  if (!proc) return '';
  const paths = getLogPaths(proc);
  // Prefer combined log (has most meaningful output), then out log
  const preferred = paths.combinedLog || paths.outLog;
  if (!preferred || !existsSync(preferred)) return '';
  try {
    const line = execSync(`tail -n 1 ${JSON.stringify(preferred)}`, { timeout: 2000, encoding: 'utf-8' }).trim();
    return line;
  } catch {
    return '';
  }
}

function extractPm2Info(proc: any, includeLastLog = false) {
  if (!proc) return { status: 'not_found', pid: null, memory: null, cpu: null, uptime: null, restarts: null, lastLog: '' };
  const env = proc.pm2_env || {};
  const monit = proc.monit || {};
  const uptimeMs = env.pm_uptime ? Date.now() - env.pm_uptime : 0;
  return {
    status: env.status || 'unknown',
    pid: proc.pid || null,
    memory: monit.memory ? Math.round(monit.memory / 1024 / 1024) : null,
    cpu: monit.cpu ?? null,
    uptime: uptimeMs > 0 ? formatUptime(uptimeMs) : null,
    restarts: env.restart_time ?? 0,
    lastLog: includeLastLog ? getLastLogLine(proc) : '',
  };
}

// GET /api/agents — all agents with live PM2 status
router.get('/', async (_req, res) => {
  try {
    const { rows: agents } = await pool.query(
      'SELECT * FROM crm_agents ORDER BY project, pm2_name'
    );
    const procs = getPm2Status();
    const merged = agents.map((a: any) => {
      const proc = procs.find((p: any) => p.name === a.pm2_name);
      return { ...a, pm2: extractPm2Info(proc, true) };
    });
    res.json({ agents: merged });
  } catch (err: any) {
    console.error('Agents list error:', err);
    res.status(500).json({ error: err.message });
  }
});

// GET /api/agents/:name — single agent detail
router.get('/:name', async (req, res) => {
  try {
    const { rows } = await pool.query(
      'SELECT * FROM crm_agents WHERE pm2_name = $1',
      [req.params.name]
    );
    if (rows.length === 0) {
      return res.status(404).json({ error: 'Agent not found' });
    }
    const agent = rows[0];
    const proc = findPm2Process(agent.pm2_name);
    res.json({ agent: { ...agent, pm2: extractPm2Info(proc) } });
  } catch (err: any) {
    console.error('Agent detail error:', err);
    res.status(500).json({ error: err.message });
  }
});

// POST /api/agents/:name/restart
router.post('/:name/restart', async (req, res) => {
  try {
    const { name } = req.params;
    execSync(`pm2 restart ${JSON.stringify(name)}`, { timeout: 15000 });
    pm2Cache.ts = 0; // bust cache
    res.json({ message: `Restarted ${name}` });
  } catch (err: any) {
    res.status(500).json({ error: `Failed to restart: ${err.message}` });
  }
});

// POST /api/agents/:name/stop
router.post('/:name/stop', async (req, res) => {
  try {
    const { name } = req.params;
    execSync(`pm2 stop ${JSON.stringify(name)}`, { timeout: 15000 });
    pm2Cache.ts = 0;
    res.json({ message: `Stopped ${name}` });
  } catch (err: any) {
    res.status(500).json({ error: `Failed to stop: ${err.message}` });
  }
});

// POST /api/agents/:name/start
router.post('/:name/start', async (req, res) => {
  try {
    const { name } = req.params;
    execSync(`pm2 start ${JSON.stringify(name)}`, { timeout: 15000 });
    pm2Cache.ts = 0;
    res.json({ message: `Started ${name}` });
  } catch (err: any) {
    res.status(500).json({ error: `Failed to start: ${err.message}` });
  }
});

// GET /api/agents/:name/logs?lines=100
router.get('/:name/logs', async (req, res) => {
  try {
    const { name } = req.params;
    const lines = Math.min(parseInt(req.query.lines as string) || 100, 500);

    const proc = findPm2Process(name);
    if (!proc) {
      return res.json({ stdout: '', stderr: '', combined: '', outLog: '', errLog: '', combinedLog: '' });
    }

    const paths = getLogPaths(proc);
    const stdout = tailFile(paths.outLog, lines);
    const stderr = tailFile(paths.errLog, lines);
    // Combined log is often the "real" activity log (custom pm2 log path)
    const combined = paths.combinedLog && paths.combinedLog !== paths.outLog
      ? tailFile(paths.combinedLog, lines)
      : '';

    res.json({
      stdout,
      stderr,
      combined,
      outLog: paths.outLog,
      errLog: paths.errLog,
      combinedLog: paths.combinedLog !== paths.outLog ? paths.combinedLog : '',
    });
  } catch (err: any) {
    res.status(500).json({ error: `Failed to read logs: ${err.message}` });
  }
});

// GET /api/agents/:name/settings — read .env or config file
router.get('/:name/settings', async (req, res) => {
  try {
    const { rows } = await pool.query(
      'SELECT env_file, config_file FROM crm_agents WHERE pm2_name = $1',
      [req.params.name]
    );
    if (rows.length === 0) return res.status(404).json({ error: 'Agent not found' });

    const { env_file, config_file } = rows[0];
    const filePath = env_file || config_file;
    if (!filePath) return res.json({ settings: [], filePath: null, fileType: null });

    if (!existsSync(filePath)) {
      return res.json({ settings: [], filePath, fileType: null, error: 'File not found on disk' });
    }

    const content = readFileSync(filePath, 'utf-8');
    const fileType = filePath.endsWith('.py') ? 'python' : 'env';
    const settings: { key: string; value: string; sensitive: boolean }[] = [];

    const sensitivePattern = /PASSWORD|SECRET|KEY|TOKEN|API_KEY|PRIVATE/i;

    for (const line of content.split('\n')) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;

      if (fileType === 'env') {
        const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
        if (match) {
          const key = match[1];
          let value = match[2];
          // Strip surrounding quotes
          if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
            value = value.slice(1, -1);
          }
          settings.push({ key, value, sensitive: sensitivePattern.test(key) });
        }
      } else {
        // Python config: KEY = "value" or KEY = value
        const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$/);
        if (match) {
          const key = match[1];
          let value = match[2].trim();
          if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
            value = value.slice(1, -1);
          }
          settings.push({ key, value, sensitive: sensitivePattern.test(key) });
        }
      }
    }

    res.json({ settings, filePath, fileType });
  } catch (err: any) {
    console.error('Settings read error:', err);
    res.status(500).json({ error: err.message });
  }
});

// PUT /api/agents/:name/settings — write settings back
router.put('/:name/settings', async (req, res) => {
  try {
    const { rows } = await pool.query(
      'SELECT env_file, config_file FROM crm_agents WHERE pm2_name = $1',
      [req.params.name]
    );
    if (rows.length === 0) return res.status(404).json({ error: 'Agent not found' });

    const { env_file, config_file } = rows[0];
    const filePath = env_file || config_file;
    if (!filePath) return res.status(400).json({ error: 'No config file configured for this agent' });

    if (!existsSync(filePath)) {
      return res.status(400).json({ error: 'Config file not found on disk' });
    }

    const { settings, restart } = req.body;
    if (!Array.isArray(settings)) {
      return res.status(400).json({ error: 'settings must be an array of {key, value}' });
    }

    // Create backup
    const backupPath = `${filePath}.bak.${Date.now()}`;
    copyFileSync(filePath, backupPath);

    const fileType = filePath.endsWith('.py') ? 'python' : 'env';
    const originalContent = readFileSync(filePath, 'utf-8');
    const settingsMap = new Map(settings.map((s: any) => [s.key, s.value]));

    // Rebuild file preserving comments and order
    const newLines: string[] = [];
    const writtenKeys = new Set<string>();

    for (const line of originalContent.split('\n')) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) {
        newLines.push(line);
        continue;
      }

      let key: string | null = null;
      if (fileType === 'env') {
        const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);
        if (match) key = match[1];
      } else {
        const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
        if (match) key = match[1];
      }

      if (key && settingsMap.has(key)) {
        const newVal = settingsMap.get(key)!;
        if (fileType === 'env') {
          // Quote if contains spaces or special chars
          const needsQuote = /[\s#"'\\]/.test(newVal);
          newLines.push(`${key}=${needsQuote ? `"${newVal}"` : newVal}`);
        } else {
          newLines.push(`${key} = "${newVal}"`);
        }
        writtenKeys.add(key);
      } else {
        newLines.push(line);
      }
    }

    // Append any new keys not in original
    for (const [key, value] of settingsMap.entries()) {
      if (!writtenKeys.has(key)) {
        if (fileType === 'env') {
          const needsQuote = /[\s#"'\\]/.test(value as string);
          newLines.push(`${key}=${needsQuote ? `"${value}"` : value}`);
        } else {
          newLines.push(`${key} = "${value}"`);
        }
      }
    }

    writeFileSync(filePath, newLines.join('\n'));

    // Optionally restart
    if (restart) {
      try {
        execSync(`pm2 restart ${JSON.stringify(req.params.name)}`, { timeout: 15000 });
        pm2Cache.ts = 0;
      } catch { /* restart failed, settings still saved */ }
    }

    res.json({ message: 'Settings saved', backupPath });
  } catch (err: any) {
    console.error('Settings write error:', err);
    res.status(500).json({ error: err.message });
  }
});

export default router;
