require('dotenv').config({ path: require('path').join(__dirname, '.env') });
const express  = require('express');
const cors     = require('cors');
const http     = require('http');
const { WebSocketServer } = require('ws');
const bcrypt   = require('bcryptjs');
const fs       = require('fs');
const path     = require('path');

const app    = express();
const server = http.createServer(app);
const wss    = new WebSocketServer({ server });
const backupRoutes = require('./routes/backup');
const gitRoutes    = require('./routes/git');
const redirectsRouter = require('./routes/redirects');

// ── Server startup timestamp ────────────────────────────────
const SERVER_START = Date.now();
app.get('/api/ping', (req, res) => res.json({ ok: true, started: SERVER_START }));

app.use(redirectsRouter.redirectMiddleware); // ← BEFORE all other routes
app.use(cors({ origin: '*' }));
app.use(express.json());

// ── Server-side cache ──────────────────────────────────────
const _sCache = {};
function sCache(key, ttlMs, fn) {
  const now = Date.now();
  if (_sCache[key] && (now - _sCache[key].ts) < ttlMs) {
    return Promise.resolve(_sCache[key].data);
  }
  return Promise.resolve(fn()).then(data => {
    _sCache[key] = { data, ts: now };
    return data;
  });
}
global.sCache = sCache;
global.sCacheClear = (key) => delete _sCache[key];

// ── Quick backup endpoint ──────────────────────────────────
app.post('/api/backup', require('./middleware/auth'), (req, res) => {
  const fs   = require('fs');
  const path = require('path');
  const { execSync } = require('child_process');
  try {
    const stamp   = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const backDir = path.join(__dirname, 'data', 'backups');
    const zipPath = path.join(backDir, `panel-data-${stamp}.zip`);
    fs.mkdirSync(backDir, { recursive: true });
    execSync(`powershell -ExecutionPolicy Bypass -Command "Compress-Archive -Path '${path.join(__dirname, 'data')}\\*' -DestinationPath '${zipPath}' -Force"`, { timeout: 60000 });
    const stat = fs.statSync(zipPath);
    require('./routes/audit').writeAudit(req, 'backup.data', `Created ${path.basename(zipPath)} (${Math.round(stat.size/1024)} KB)`);
    res.json({ ok: true, file: path.basename(zipPath), size: stat.size, path: zipPath });
  } catch(e) { res.status(500).json({ error: e.message }); }
});

//Restart Node after updating git dependencies
app.post('/api/system/restart', (req, res) => {
  res.json({ ok: true, message: 'Restarting...' });
  setTimeout(function() {
    process.exit(0); // PM2 or nodemon will restart automatically
  }, 500);
});

// ── npm install for optional packages ─────────────────────
app.post('/api/system/npm-install', (req, res) => {
  const { exec } = require('child_process');
  const packages  = (req.body.packages || []).join(' ');
  if (!packages) return res.json({ ok: false, error: 'No packages specified' });

  exec('npm install ' + packages, { cwd: path.join(__dirname) }, (err, stdout, stderr) => {
    if (err) return res.json({ ok: false, error: stderr || err.message });
    res.json({ ok: true, output: stdout });
  });
});

app.get('/api/backup/list', require('./middleware/auth'), (req, res) => {
  const fs   = require('fs');
  const path = require('path');
  try {
    const backDir = path.join(__dirname, 'data', 'backups');
    if (!fs.existsSync(backDir)) return res.json([]);
    const files = fs.readdirSync(backDir)
      .filter(f => f.startsWith('panel-data-') && f.endsWith('.zip'))
      .map(f => {
        const stat = fs.statSync(path.join(backDir, f));
        return { name: f, size: stat.size, created: stat.birthtime };
      })
      .sort((a, b) => new Date(b.created) - new Date(a.created));
    res.json(files);
  } catch(e) { res.status(500).json({ error: e.message }); }
});

app.delete('/api/backup/:name', require('./middleware/auth'), (req, res) => {
  const fs   = require('fs');
  const path = require('path');
  try {
    const file = path.basename(req.params.name); // strip any path traversal
    if (!file.startsWith('panel-data-')) return res.status(400).json({ error: 'Invalid file' });
    fs.unlinkSync(path.join(__dirname, 'data', 'backups', file));
    res.json({ ok: true });
  } catch(e) { res.status(500).json({ error: e.message }); }
});

app.get('/install.html', (_req, res) => res.sendFile(path.join(__dirname, 'install.html')));
app.get('/tests.html',   (_req, res) => res.sendFile(path.join(__dirname, 'tests.html')));
app.use(express.static('client'));

// ── Seed users.json on first boot ─────────────────────────
(function seedUsers() {
  const DB = path.join(__dirname, 'data/users.json');
  if (fs.existsSync(DB)) return;
  const ALL_PERMS = ['dashboard','processes','sites','ssl','services','database','query',
                     'dbusers','files','logs','smtp','domains','users','settings'];
  const hash = process.env.ADMIN_PASS_HASH || bcrypt.hashSync('admin123', 10);
  const seed = [{ id: 1, username: process.env.ADMIN_USER || 'admin', password: hash,
    role: 'admin', permissions: ALL_PERMS, createdAt: new Date().toISOString() }];
  try {
    fs.mkdirSync(path.dirname(DB), { recursive: true });
    fs.writeFileSync(DB, JSON.stringify(seed, null, 2));
    console.log(`[startup] Seeded admin user: "${seed[0].username}"`);
  } catch(e) { console.error('[startup] Seed failed:', e.message); }
})();


// ── Routes ─────────────────────────────────────────────────
const systemRoute = require('./routes/system');

app.use('/api/auth',      require('./routes/auth'));
app.use('/api/system',    systemRoute);
app.use('/api/sites',     require('./routes/sites'));
app.use('/api/services',  require('./routes/services'));
app.use('/api/files',     require('./routes/files'));
app.use('/api/processes', require('./routes/processes'));
app.use('/api/ssl',       require('./routes/ssl'));
app.use('/api/db',        require('./routes/database'));
app.use('/api/users',     require('./routes/users'));
app.use('/api/logs',      require('./routes/logs'));
app.use('/api/smtp',      require('./routes/smtp'));
app.use('/api/domains',   require('./routes/domains'));
app.use('/api/settings',  require('./routes/settings'));
app.use('/api/audit',     require('./routes/audit'));
app.use('/api/apppools',  require('./routes/apppools'));
app.use('/api/traffic',   require('./routes/traffic'));
app.use('/api/security',    require('./routes/security'));
app.use('/api/scheduler',   require('./routes/scheduler'));
app.use('/api/letsencrypt', require('./routes/letsencrypt'));
app.use('/api/dotnet',   require('./routes/dotnet'));
app.use('/api/prereqs', require('./routes/prereqs'));
app.use('/api/docker', require('./routes/docker'));
app.use('/api/backups', require('./routes/backup'));
app.use('/api/git',     require('./routes/git'));
app.use('/api/update',  require('./routes/update'));
app.use('/api/testrunner', require('./routes/testrunner'));

// ── WebSocket — live stats feed ────────────────────────────
const { collectStats } = systemRoute;

wss.on('connection', ws => {
  try { ws.send(JSON.stringify({ type: 'stats', ...collectStats() })); } catch {}
  const id = setInterval(() => {
    if (ws.readyState !== ws.OPEN) return;
    try { ws.send(JSON.stringify({ type: 'stats', ...collectStats() })); } catch {}
  }, 2000);
  ws.on('close', () => clearInterval(id));
  ws.on('error', () => clearInterval(id));
});

// ── Crash monitor (must be last) ───────────────────────────
require('./crashMonitor').init();

const PORT = process.env.PORT || 4080;
server.listen(PORT, () => console.log(`ServerPanel running on :${PORT}`));