const express = require('express');
const router  = express.Router();
const { getDb, getSetting, setSetting, rollbackMigration } = require('../db/database');
const { hashPassword } = require('../services/authService');
const {
  createBackup,
  deleteBackup,
  getBackupFile,
  importBackupBuffer,
  listBackups,
  restoreBackup,
} = require('../services/backupService');
const {
  getScheduleStatus,
  runScheduledBackupNow,
  saveSettings: saveBackupScheduleSettings,
} = require('../services/backupScheduler');
const {
  getCleanupStatus,
  runAllCleanup,
  validateAndApplySettings: applyCleanupSettings,
} = require('../services/cleanupService');
const { backupOperationLimiter } = require('../middleware/rateLimiter');

// All routes mounted at /api/admin (requireAuth + requireAdmin applied at server level)

function sendError(res, err) {
  const status = err.status || 500;
  res.status(status).json({ error: status === 500 ? 'Backup operation failed' : err.message });
}

// GET /api/admin/has-users
router.get('/has-users', (req, res) => {
  const count = getDb().prepare('SELECT COUNT(*) AS n FROM users WHERE id != ?').get(req.user.id).n;
  res.json({ has_users: count > 0 });
});

// GET /api/admin/users
router.get('/users', (req, res) => {
  res.json(
    getDb().prepare(
      'SELECT id, username, role, active, is_default_admin, must_change_password, first_login, created_at FROM users ORDER BY is_default_admin DESC, role DESC, username ASC'
    ).all()
  );
});

// POST /api/admin/backups
router.post('/backups', backupOperationLimiter, async (req, res) => {
  try {
    const backup = await createBackup();
    res.status(201).json(backup);
  } catch (err) {
    sendError(res, err);
  }
});

// GET /api/admin/backups
router.get('/backups', backupOperationLimiter, (req, res) => {
  try {
    res.json({ backups: listBackups() });
  } catch (err) {
    sendError(res, err);
  }
});

// GET /api/admin/backups/settings
router.get('/backups/settings', backupOperationLimiter, (req, res) => {
  try {
    res.json(getScheduleStatus());
  } catch (err) {
    sendError(res, err);
  }
});

// PUT /api/admin/backups/settings
router.put('/backups/settings', backupOperationLimiter, (req, res) => {
  try {
    res.json(saveBackupScheduleSettings(req.body));
  } catch (err) {
    sendError(res, err);
  }
});

// POST /api/admin/backups/run-scheduled-now
router.post('/backups/run-scheduled-now', backupOperationLimiter, async (req, res) => {
  try {
    res.status(201).json(await runScheduledBackupNow());
  } catch (err) {
    sendError(res, err);
  }
});

// POST /api/admin/backups/import
router.post(
  '/backups/import',
  backupOperationLimiter,
  express.raw({
    type: ['application/octet-stream', 'application/x-sqlite3', 'application/vnd.sqlite3'],
    limit: '100mb',
  }),
  async (req, res) => {
    try {
      // Extract expected checksum from request headers or query
      const expectedChecksum = req.headers['x-checksum-sha256'] || req.query.checksum;
      
      const backup = await importBackupBuffer(req.body, {
        expectedChecksum: expectedChecksum ? String(expectedChecksum).trim() : undefined,
      });
      res.status(201).json(backup);
    } catch (err) {
      sendError(res, err);
    }
  },
);

// GET /api/admin/backups/:id/download
router.get('/backups/:id/download', (req, res) => {
  try {
    const backup = getBackupFile(req.params.id);
    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.download(backup.path, backup.metadata.id, (err) => {
      if (err && !res.headersSent) sendError(res, err);
    });
  } catch (err) {
    sendError(res, err);
  }
});

// POST /api/admin/backups/:id/restore
router.post('/backups/:id/restore', backupOperationLimiter, async (req, res) => {
  try {
    res.json(await restoreBackup(req.params.id));
  } catch (err) {
    sendError(res, err);
  }
});

// DELETE /api/admin/backups/:id
router.delete('/backups/:id', backupOperationLimiter, (req, res) => {
  try {
    res.json(deleteBackup(req.params.id));
  } catch (err) {
    sendError(res, err);
  }
});

// POST /api/admin/users
router.post('/users', async (req, res) => {
  const { username, password } = req.body;
  if (!username || username.length < 3)
    return res.status(400).json({ error: 'Username must be at least 3 characters' });
  if (!password || password.length < 8)
    return res.status(400).json({ error: 'Password must be at least 8 characters' });

  const db = getDb();
  if (db.prepare('SELECT id FROM users WHERE username = ?').get(username))
    return res.status(409).json({ error: 'Username already taken' });

  const hash   = await hashPassword(password);
  const result = db.prepare(
    "INSERT INTO users (username, password_hash, role, first_login) VALUES (?, ?, 'user', 1)"
  ).run(username, hash);

  res.status(201).json(
    db.prepare('SELECT id, username, role, active, is_default_admin, must_change_password, first_login, created_at FROM users WHERE id = ?')
      .get(result.lastInsertRowid)
  );
});

// PUT /api/admin/users/:id/password
router.put('/users/:id/password', async (req, res) => {
  const { password } = req.body;
  if (!password || password.length < 8)
    return res.status(400).json({ error: 'Password must be at least 8 characters' });

  const db   = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.params.id);
  if (!user)               return res.status(404).json({ error: 'User not found' });

  const hash = await hashPassword(password);
  db.prepare("UPDATE users SET password_hash=?, must_change_password=1, updated_at=datetime('now') WHERE id=?")
    .run(hash, req.params.id);
  db.prepare('DELETE FROM sessions WHERE user_id = ?').run(req.params.id);
  res.json({ success: true });
});

// Import audit service
const { logAudit } = require('../services/auditService');

// PUT /api/admin/users/:id/role
// Promote/demote an existing user. Prevents removing the last admin or
// changing your own role mid-session.
router.put('/users/:id/role', (req, res) => {
  const { role } = req.body;
  if (!['admin', 'user'].includes(role)) {
    return res.status(400).json({ error: 'role must be "admin" or "user"' });
  }

  const targetId = Number(req.params.id);
  const db       = getDb();
  const user     = db.prepare('SELECT * FROM users WHERE id = ?').get(targetId);
  if (!user) return res.status(404).json({ error: 'User not found' });

  if (req.user?.id === targetId) {
    return res.status(400).json({ error: 'You cannot change your own admin role.' });
  }

  if (user.role === 'admin' && role === 'user') {
    const adminCount = db.prepare("SELECT COUNT(*) AS n FROM users WHERE role = 'admin'").get().n;
    if (adminCount <= 1) {
      return res.status(400).json({ error: 'Cannot remove the last admin account.' });
    }
  }

  // SECURITY FIX (2026-05-08): Delete all sessions for the target user when role changes.
  // This forces re-authentication with the new role, preventing session hijacking
  // from being used to bypass privilege checks.
  db.prepare('DELETE FROM sessions WHERE user_id = ?').run(targetId);

  const previousRole = user.role;
  db.prepare("UPDATE users SET role = ?, updated_at = datetime('now') WHERE id = ?")
    .run(role, targetId);

  logAudit({ user_id: req.user.id, action: 'role.change', entity_type: 'user', entity_id: targetId, details: { old_role: previousRole, new_role: role }, ip_address: req.ip, user_agent: req.get('user-agent') });

  const updated = db.prepare(
    'SELECT id, username, role, active, is_default_admin, must_change_password, first_login, created_at FROM users WHERE id = ?'
  ).get(targetId);

  res.json(updated);
});

// PUT /api/admin/users/:id/active
router.put('/users/:id/active', (req, res) => {
  const active = req.body?.active ? 1 : 0;
  const targetId = Number(req.params.id);
  const db = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(targetId);
  if (!user) return res.status(404).json({ error: 'User not found' });
  if (req.user?.id === targetId) {
    return res.status(400).json({ error: 'You cannot deactivate your own account.' });
  }

  db.prepare("UPDATE users SET active = ?, updated_at = datetime('now') WHERE id = ?").run(active, targetId);
  if (!active) db.prepare('DELETE FROM sessions WHERE user_id = ?').run(targetId);

  res.json(db.prepare(
    'SELECT id, username, role, active, is_default_admin, must_change_password, first_login, created_at FROM users WHERE id = ?'
  ).get(targetId));
});

// PUT /api/admin/users/:id/username
router.put('/users/:id/username', (req, res) => {
  const { username } = req.body;
  if (!username || typeof username !== 'string') {
    return res.status(400).json({ error: 'username is required' });
  }
  const trimmed = username.trim();
  if (trimmed.length < 3) {
    return res.status(400).json({ error: 'Username must be at least 3 characters' });
  }
  if (trimmed.length > 50) {
    return res.status(400).json({ error: 'Username must be 50 characters or fewer' });
  }

  const targetId = Number(req.params.id);
  const db       = getDb();
  const user     = db.prepare('SELECT id, username FROM users WHERE id = ?').get(targetId);
  if (!user) return res.status(404).json({ error: 'User not found' });

  const taken = db.prepare(
    'SELECT id FROM users WHERE username = ? COLLATE NOCASE AND id != ?'
  ).get(trimmed, targetId);
  if (taken) return res.status(409).json({ error: 'Username already taken' });

  const previousUsername = user.username;
  db.prepare("UPDATE users SET username = ?, updated_at = datetime('now') WHERE id = ?")
    .run(trimmed, targetId);

  logAudit({
    user_id: req.user.id, action: 'admin.username.change',
    entity_type: 'user', entity_id: targetId,
    details: { old_username: previousUsername, new_username: trimmed },
    ip_address: req.ip, user_agent: req.get('user-agent'),
  });

  res.json(
    db.prepare('SELECT id, username, role, active, is_default_admin, must_change_password, first_login, created_at FROM users WHERE id = ?')
      .get(targetId)
  );
});

// DELETE /api/admin/users/:id
router.delete('/users/:id', (req, res) => {
  const db   = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.params.id);
  if (!user)               return res.status(404).json({ error: 'User not found' });
  if (req.user?.id === user.id) return res.status(400).json({ error: 'You cannot delete your own account.' });

  const deleteUser = db.transaction(() => {
    db.prepare('DELETE FROM import_sessions WHERE user_id = ?').run(user.id);
    db.prepare('DELETE FROM import_history WHERE user_id = ?').run(user.id);
    db.prepare('DELETE FROM sessions WHERE user_id = ?').run(user.id);
    db.prepare('DELETE FROM users WHERE id = ?').run(user.id);
  });
  deleteUser();
  res.json({ success: true, deleted_user_id: user.id });
});

// ── Cleanup endpoints ─────────────────────────────────────────────────────────

// GET /api/admin/cleanup
// Returns current cleanup settings and the result of the last cleanup run.
router.get('/cleanup', (req, res) => {
  try {
    res.json(getCleanupStatus());
  } catch (err) {
    sendError(res, err);
  }
});


// PUT /api/admin/cleanup
// Updates one or more cleanup settings. Accepts partial objects.
//   import_sessions_enabled        boolean   prune expired import preview sessions
//   temp_exports_enabled           boolean   prune stale SQLite export temp files
//   temp_export_max_age_hours      1–72      hours before an orphaned export file is removed
//   backup_partials_enabled        boolean   prune orphaned .partial/.upload backup files
//   import_history_enabled         boolean   prune old import history rows (disabled by default)
//   import_history_max_age_days    30–3650   age threshold for import history rows
router.put('/cleanup', (req, res) => {
  try {
    res.json(applyCleanupSettings(req.body));
  } catch (err) {
    sendError(res, err);
  }
});

// POST /api/admin/cleanup/run
// Runs all enabled cleanup tasks immediately and returns the result.
router.post('/cleanup/run', backupOperationLimiter, async (req, res) => {
  try {
    const result = await runAllCleanup();
    res.json(result);
  } catch (err) {
    sendError(res, err);
  }
});

// ── Auth-mode helpers ─────────────────────────────────────────────────────────

const {
  getAdminOidcSettings,
  getOidcConfigStatus,
  invalidateClientCache,
  testOidcConfiguration,
} = require('../services/oidcService');

function trimOrEmpty(value) {
  if (value === undefined || value === null) return '';
  return String(value).trim();
}

function boolSetting(value, fallback) {
  if (value === undefined) return fallback;
  if (typeof value === 'string') return value === 'true';
  return !!value;
}

function computeSubmittedOidcConfigured(body) {
  const current = getAdminOidcSettings();
  const next = {
    issuer: body.oidc_issuer_url !== undefined
      ? trimOrEmpty(body.oidc_issuer_url)
      : current.oidc_issuer_url,
    clientId: body.oidc_client_id !== undefined
      ? trimOrEmpty(body.oidc_client_id)
      : current.oidc_client_id,
    redirectUri: body.oidc_redirect_uri !== undefined
      ? trimOrEmpty(body.oidc_redirect_uri)
      : current.oidc_redirect_uri,
    clientSecret: current.oidc_client_secret_set ? 'set' : '',
  };

  if (body.oidc_client_secret_clear === true) {
    next.clientSecret = process.env.OIDC_CLIENT_SECRET ? 'set' : '';
  }
  if (trimOrEmpty(body.oidc_client_secret)) {
    next.clientSecret = 'set';
  }

  return !!(next.issuer && next.clientId && next.clientSecret && next.redirectUri);
}

function buildSubmittedOidcConfig(body) {
  const current = getAdminOidcSettings();
  const status = getOidcConfigStatus();

  const issuerUrl = body.oidc_issuer_url !== undefined
    ? trimOrEmpty(body.oidc_issuer_url)
    : current.oidc_issuer_url;
  const clientId = body.oidc_client_id !== undefined
    ? trimOrEmpty(body.oidc_client_id)
    : current.oidc_client_id;
  const redirectUri = body.oidc_redirect_uri !== undefined
    ? trimOrEmpty(body.oidc_redirect_uri)
    : current.oidc_redirect_uri;
  const tokenAuthMethod = body.oidc_token_auth_method !== undefined
    ? trimOrEmpty(body.oidc_token_auth_method)
    : current.oidc_token_auth_method;
  const scopes = body.oidc_scopes !== undefined
    ? trimOrEmpty(body.oidc_scopes)
    : current.oidc_scopes;
  const providerName = body.oidc_provider_name !== undefined
    ? trimOrEmpty(body.oidc_provider_name)
    : current.oidc_provider_name;

  let clientSecret = status.oidc_client_secret_set ? '__saved__' : '';
  if (body.oidc_client_secret_clear === true) clientSecret = process.env.OIDC_CLIENT_SECRET || '';
  if (trimOrEmpty(body.oidc_client_secret)) clientSecret = trimOrEmpty(body.oidc_client_secret);

  if (!issuerUrl || !clientId || !clientSecret || !redirectUri) return null;

  return {
    enabled: true,
    issuerUrl,
    clientId,
    clientSecret: clientSecret === '__saved__'
      ? (getSetting('oidc_client_secret') || process.env.OIDC_CLIENT_SECRET || '')
      : clientSecret,
    tokenEndpointAuthMethod: tokenAuthMethod === 'client_secret_post'
      ? 'client_secret_post'
      : 'client_secret_basic',
    redirectUri,
    scopes: (scopes || 'openid email profile groups').split(/\s+/).filter(Boolean),
    adminGroup: body.oidc_admin_group !== undefined ? trimOrEmpty(body.oidc_admin_group) : current.oidc_admin_group,
    defaultRole: 'user',
    autoProvision: body.oidc_auto_provision !== undefined ? !!body.oidc_auto_provision : current.oidc_auto_provision,
    providerName: providerName || 'authentik',
  };
}

function buildAuthModeStatus() {
  const oidcConfigured = getOidcConfigStatus().oidc_configured;
  const localEnabled   = getSetting('local_login_enabled') !== 'false';
  const oidcEnabled    = getSetting('oidc_login_enabled') === 'true';
  const oidcAdminGroup = getAdminOidcSettings().oidc_admin_group;

  // Disabling local is only safe if OIDC is configured, enabled, and has an admin path.
  const canDisableLocal = oidcConfigured && oidcEnabled && !!oidcAdminGroup;

  const warnings = [];
  if (!localEnabled && !oidcConfigured) {
    warnings.push('Local login is disabled but OIDC is not configured; users may be locked out.');
  }
  if (!localEnabled && !oidcEnabled) {
    warnings.push('No login method is enabled. Re-enable local login or configure OIDC.');
  }
  if (oidcEnabled && !oidcConfigured) {
    warnings.push('authentik/OIDC login is enabled but configuration is incomplete, so the login button will stay hidden.');
  }
  if (!localEnabled && !oidcAdminGroup) {
    warnings.push('Local login is disabled but no OIDC admin group is configured.');
  }

  return {
    auth_mode:        getSetting('auth_mode') || 'multi',
    default_user_id:  getSetting('default_user_id') || null,
    local_login_enabled: localEnabled,
    oidc_login_enabled:  oidcEnabled,
    oidc_configured:     oidcConfigured,
    ...getOidcConfigStatus(),
    ...getAdminOidcSettings(),
    can_disable_local:   canDisableLocal,
    warnings,
  };
}

// GET /api/admin/auth-mode
router.get('/auth-mode', (req, res) => {
  res.json(buildAuthModeStatus());
});

// POST /api/admin/auth-mode/oidc-test
// Tests submitted or saved OIDC provider settings with OIDC discovery.
// Never returns client secret or token material.
router.post('/auth-mode/oidc-test', async (req, res) => {
  const config = buildSubmittedOidcConfig(req.body || {});
  const result = await testOidcConfiguration(config);
  res.status(result.ok ? 200 : 400).json(result);
});

// PUT /api/admin/auth-mode
// Accepts legacy auth_mode/default_user_id fields plus new auth method settings.
// Validates lockout protection before saving.
router.put('/auth-mode', (req, res) => {
  const {
    auth_mode, default_user_id,
    local_login_enabled, oidc_login_enabled, oidc_enabled,
    oidc_provider_name, oidc_issuer_url, oidc_client_id, oidc_client_secret,
    oidc_client_secret_clear, oidc_token_auth_method, oidc_redirect_uri, oidc_scopes,
    oidc_auto_provision, oidc_admin_group, oidc_default_role,
  } = req.body;

  // ── Legacy single/multi mode (unchanged behavior) ─────────────────────────
  if (auth_mode !== undefined) {
    if (!['multi', 'single'].includes(auth_mode))
      return res.status(400).json({ error: 'auth_mode must be "multi" or "single"' });
    if (auth_mode === 'single') {
      if (!default_user_id) return res.status(400).json({ error: 'default_user_id is required for single mode' });
      const u = getDb().prepare("SELECT id FROM users WHERE id=? AND role='user'").get(default_user_id);
      if (!u) return res.status(404).json({ error: 'User not found or not a regular user' });
      setSetting('default_user_id', default_user_id);
    }
    setSetting('auth_mode', auth_mode);
  }

  // ── Auth method toggles ───────────────────────────────────────────────────
  const oidcConfigured = computeSubmittedOidcConfigured(req.body || {});
  const nextLocal      = boolSetting(local_login_enabled, getSetting('local_login_enabled') !== 'false');
  const requestedOidc  = oidc_login_enabled !== undefined ? oidc_login_enabled : oidc_enabled;
  const nextOidc       = boolSetting(requestedOidc, getSetting('oidc_login_enabled') === 'true');
  const nextAdminGroup = oidc_admin_group !== undefined
    ? trimOrEmpty(oidc_admin_group)
    : getAdminOidcSettings().oidc_admin_group;

  // Lockout protection: cannot disable both login methods
  if (!nextLocal && !nextOidc) {
    return res.status(400).json({ error: 'Cannot disable all login methods. At least one must remain enabled.' });
  }

  // Lockout protection: cannot disable local login unless OIDC has a working admin path.
  if (!nextLocal && !oidcConfigured) {
    return res.status(400).json({
      error: 'Cannot disable local login until authentik/OIDC is fully configured.',
    });
  }
  if (!nextLocal && !nextOidc) {
    return res.status(400).json({
      error: 'Cannot disable local login without OIDC login enabled.',
    });
  }
  if (!nextLocal && !nextAdminGroup) {
    return res.status(400).json({
      error: 'Cannot disable local login until an OIDC admin group is configured.',
    });
  }

  // Cannot enable OIDC login if required provider settings are incomplete
  if (nextOidc && !oidcConfigured) {
    return res.status(400).json({
      error: 'Cannot enable OIDC login until issuer URL, client ID, client secret, and redirect URI are configured.',
    });
  }

  if (local_login_enabled !== undefined) setSetting('local_login_enabled', nextLocal ? 'true' : 'false');
  if (oidc_login_enabled  !== undefined) setSetting('oidc_login_enabled',  nextOidc ? 'true' : 'false');

  // OIDC provider settings. Client secret is write-only from the Admin API.
  if (oidc_provider_name !== undefined) {
    const name = String(oidc_provider_name).slice(0, 100).trim();
    if (name) setSetting('oidc_provider_name', name);
  }
  if (oidc_issuer_url   !== undefined) setSetting('oidc_issuer_url', trimOrEmpty(oidc_issuer_url).slice(0, 500));
  if (oidc_client_id    !== undefined) setSetting('oidc_client_id', trimOrEmpty(oidc_client_id).slice(0, 500));
  if (oidc_token_auth_method !== undefined) {
    const method = oidc_token_auth_method === 'client_secret_post' ? 'client_secret_post' : 'client_secret_basic';
    setSetting('oidc_token_auth_method', method);
  }
  if (oidc_redirect_uri !== undefined) setSetting('oidc_redirect_uri', trimOrEmpty(oidc_redirect_uri).slice(0, 500));
  if (oidc_scopes       !== undefined) {
    const scopes = trimOrEmpty(oidc_scopes).split(/\s+/).filter(Boolean).join(' ') || 'openid email profile groups';
    setSetting('oidc_scopes', scopes.slice(0, 500));
  }
  if (oidc_client_secret_clear === true) setSetting('oidc_client_secret', '');
  if (trimOrEmpty(oidc_client_secret)) {
    setSetting('oidc_client_secret', trimOrEmpty(oidc_client_secret).slice(0, 1000));
  }
  if (oidc_auto_provision !== undefined) setSetting('oidc_auto_provision', !!oidc_auto_provision ? 'true' : 'false');
  if (oidc_admin_group    !== undefined) setSetting('oidc_admin_group', String(oidc_admin_group).slice(0, 200).trim());
  if (oidc_default_role   !== undefined) {
    setSetting('oidc_default_role', 'user');
  }

  if (
    oidc_issuer_url !== undefined ||
    oidc_client_id !== undefined ||
    oidc_client_secret !== undefined ||
    oidc_client_secret_clear === true ||
    oidc_token_auth_method !== undefined ||
    oidc_redirect_uri !== undefined
  ) {
    invalidateClientCache();
  }

  res.json({ success: true, ...buildAuthModeStatus() });
});

// ── Migration Rollback ────────────────────────────────────────────────────────
router.post('/migrations/rollback', async (req, res) => {
  const { version } = req.body;
  if (!version) {
    return res.status(400).json({ error: 'Version is required' });
  }

  try {
    const result = rollbackMigration(version);
    logAudit({
      user_id: req.user.id,
      action: 'migration.rollback',
      entity_type: 'migration',
      entity_id: null,
      details: { version, performed_by: req.user.username },
      ip_address: req.ip,
      user_agent: req.get('user-agent')
    });
    res.json({ success: true, ...result });
  } catch (err) {
    logAudit({
      user_id: req.user.id,
      action: 'migration.rollback.failure',
      entity_type: 'migration',
      entity_id: null,
      details: { version, error: err.message, performed_by: req.user.username },
      ip_address: req.ip,
      user_agent: req.get('user-agent')
    });

    if (err.code === 'NOT_APPLIED') {
      return res.status(404).json({ error: err.message });
    }
    if (err.code === 'ROLLBACK_NOT_SUPPORTED') {
      return res.status(422).json({ error: err.message });
    }
    res.status(500).json({ error: 'Rollback failed', details: err.message });
  }
});

module.exports = router;
