/**
 * TASKMATE - Auth Middleware
 * ==========================
 * JWT-basierte Authentifizierung
 */

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const logger = require('../utils/logger');
const { getDb } = require('../database');

const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET || JWT_SECRET.length < 32) {
  throw new Error('JWT_SECRET muss in .env gesetzt und mindestens 32 Zeichen lang sein!');
}
const ACCESS_TOKEN_EXPIRY = 60; // Minuten (kürzer für mehr Sicherheit)
const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60; // 7 Tage in Minuten
/**
 * JWT Access-Token generieren (kurze Lebensdauer)
 */
function generateAccessToken(user) {
  // Permissions parsen falls als String gespeichert
  let permissions = user.permissions || [];
  if (typeof permissions === 'string') {
    try {
      permissions = JSON.parse(permissions);
    } catch (e) {
      permissions = [];
    }
  }

  return jwt.sign(
    {
      id: user.id,
      username: user.username,
      displayName: user.display_name,
      color: user.color,
      role: user.role || 'user',
      permissions: permissions,
      type: 'access'
    },
    JWT_SECRET,
    { expiresIn: `${ACCESS_TOKEN_EXPIRY}m` }
  );
}

/**
 * Refresh-Token generieren (lange Lebensdauer)
 */
function generateRefreshToken(userId, ipAddress, userAgent) {
  const db = getDb();
  const token = crypto.randomBytes(32).toString('hex');
  const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY * 60 * 1000);
  
  // Token in Datenbank speichern
  db.prepare(`
    INSERT INTO refresh_tokens (user_id, token, expires_at, ip_address, user_agent)
    VALUES (?, ?, ?, ?, ?)
  `).run(userId, token, expiresAt.toISOString(), ipAddress, userAgent);
  
  return token;
}

/**
 * JWT-Token verifizieren
 */
function verifyToken(token) {
  try {
    return jwt.verify(token, JWT_SECRET);
  } catch (error) {
    // Nur bei unerwarteten Fehlern loggen (nicht bei normalen Ablauf/Ungültig-Fällen)
    if (error.name !== 'TokenExpiredError' && error.name !== 'JsonWebTokenError') {
      logger.error(`[AUTH] Unerwarteter Token-Fehler: ${error.name} - ${error.message}`);
    }
    return null;
  }
}

/**
 * Express Middleware: Token aus Header, Cookie oder Query-Parameter prüfen
 */
function authenticateToken(req, res, next) {
  // Token aus Authorization Header, Cookie oder Query-Parameter (für img src etc.)
  let token = null;

  const authHeader = req.headers['authorization'];
  if (authHeader && authHeader.startsWith('Bearer ')) {
    token = authHeader.substring(7);
  } else if (req.cookies && req.cookies.token) {
    token = req.cookies.token;
  } else if (req.query && req.query.token) {
    // Token aus Query-Parameter (für Ressourcen die in img/video tags geladen werden)
    token = req.query.token;
  }

  if (!token) {
    return res.status(401).json({ error: 'Nicht authentifiziert' });
  }

  const user = verifyToken(token);
  if (!user) {
    return res.status(401).json({ error: 'Token ungültig oder abgelaufen' });
  }

  // User-Info an Request anhängen
  req.user = user;

  // Token-Refresh: Wenn Token bald ablaeuft, neuen ausstellen
  const tokenExp = user.exp * 1000; // exp ist in Sekunden
  const now = Date.now();
  const refreshThreshold = 5 * 60 * 1000; // 5 Minuten vor Ablauf

  if (tokenExp - now < refreshThreshold) {
    const newToken = generateAccessToken({
      id: user.id,
      username: user.username,
      display_name: user.displayName,
      color: user.color,
      role: user.role,
      permissions: user.permissions
    });
    res.setHeader('X-New-Token', newToken);
  }

  next();
}

/**
 * Middleware: Nur Admins erlauben
 */
function requireAdmin(req, res, next) {
  if (!req.user) {
    return res.status(401).json({ error: 'Nicht authentifiziert' });
  }

  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Keine Admin-Berechtigung' });
  }

  next();
}

/**
 * Middleware: Nur regulaere User erlauben (blockiert Admins)
 */
function requireRegularUser(req, res, next) {
  if (!req.user) {
    return res.status(401).json({ error: 'Nicht authentifiziert' });
  }

  if (req.user.role === 'admin') {
    return res.status(403).json({ error: 'Admin hat keinen Zugang zur regulaeren App' });
  }

  next();
}

/**
 * Middleware-Factory: Bestimmte Berechtigung pruefen
 */
function checkPermission(permission) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Nicht authentifiziert' });
    }

    const permissions = req.user.permissions || [];
    if (!permissions.includes(permission)) {
      return res.status(403).json({ error: `Berechtigung "${permission}" fehlt` });
    }

    next();
  };
}

/**
 * Socket.io Middleware: Token prüfen
 */
function authenticateSocket(socket, next) {
  const token = socket.handshake.auth.token ||
    socket.handshake.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return next(new Error('Nicht authentifiziert'));
  }

  const user = verifyToken(token);
  if (!user) {
    return next(new Error('Token ungültig oder abgelaufen'));
  }

  // User-Info an Socket anhängen
  socket.user = user;
  next();
}

/**
 * Refresh-Token validieren und neuen Access-Token generieren
 */
async function refreshAccessToken(refreshToken, ipAddress, userAgent) {
  const db = getDb();
  
  // Token in Datenbank suchen
  const tokenRecord = db.prepare(`
    SELECT rt.*, u.* FROM refresh_tokens rt
    JOIN users u ON rt.user_id = u.id
    WHERE rt.token = ? AND rt.expires_at > datetime('now')
  `).get(refreshToken);
  
  if (!tokenRecord) {
    throw new Error('Ungültiger oder abgelaufener Refresh-Token');
  }
  
  // Token als benutzt markieren
  db.prepare(`
    UPDATE refresh_tokens SET last_used = CURRENT_TIMESTAMP WHERE id = ?
  `).run(tokenRecord.id);
  
  // Neuen Access-Token generieren
  const user = {
    id: tokenRecord.user_id,
    username: tokenRecord.username,
    display_name: tokenRecord.display_name,
    color: tokenRecord.color,
    role: tokenRecord.role,
    permissions: tokenRecord.permissions
  };
  
  return generateAccessToken(user);
}

/**
 * Alle Refresh-Tokens eines Benutzers löschen (Logout auf allen Geräten)
 */
function revokeAllRefreshTokens(userId) {
  const db = getDb();
  db.prepare('DELETE FROM refresh_tokens WHERE user_id = ?').run(userId);
}

/**
 * Abgelaufene Refresh-Tokens aufräumen
 */
function cleanupExpiredTokens() {
  const db = getDb();
  const result = db.prepare(`
    DELETE FROM refresh_tokens WHERE expires_at < datetime('now')
  `).run();
  
  if (result.changes > 0) {
    logger.info(`Bereinigt: ${result.changes} abgelaufene Refresh-Tokens`);
  }
}

// Cleanup alle 6 Stunden
setInterval(cleanupExpiredTokens, 6 * 60 * 60 * 1000);

module.exports = {
  generateAccessToken,
  generateRefreshToken,
  refreshAccessToken,
  revokeAllRefreshTokens,
  verifyToken,
  authenticateToken,
  authenticateSocket,
  requireAdmin,
  requireRegularUser,
  checkPermission
};
