import { Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import prisma from '../lib/prisma.js';
import { AuthRequest, JwtPayload } from '../types/index.js';
import { emit as emitSecurityEvent } from '../services/securityMonitor.service.js';

export async function authenticate(
  req: AuthRequest,
  res: Response,
  next: NextFunction
): Promise<void> {
  const authHeader = req.headers.authorization;

  // Token aus Header oder Query-Parameter (für Downloads)
  let token: string | null = null;
  let tokenSource: 'header' | 'query' | null = null;

  if (authHeader && authHeader.startsWith('Bearer ')) {
    token = authHeader.split(' ')[1];
    tokenSource = 'header';
  } else if (req.query.token && typeof req.query.token === 'string') {
    // Fallback für Downloads: Token als Query-Parameter
    token = req.query.token;
    tokenSource = 'query';
  }

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

  try {
    // JWT_SECRET wird beim Server-Start geprüft (Fail-Fast in index.ts)
    // Algorithmus explizit auf HS256 festlegen (Defense-in-Depth gegen alg-confusion).
    const decoded = jwt.verify(token, process.env.JWT_SECRET as string, {
      algorithms: ['HS256'],
    }) as JwtPayload & { type?: string };

    // Defense-in-Depth: Refresh-Tokens haben `type: 'refresh'` und dürfen
    // NICHT für normale API-Calls verwendet werden – nur am /api/auth/refresh-
    // Endpoint. Legacy-Tokens (vor der Refresh-Token-Einführung) haben kein
    // `type` und werden als Access akzeptiert, damit bestehende Sessions nicht
    // zwangsabgemeldet werden.
    if (decoded.type === 'refresh') {
      res.status(401).json({ success: false, error: 'Falscher Token-Typ' });
      return;
    }
    // Download-Tokens sind kurzlebig (60s) und dürfen NUR per `?token=`
    // genutzt werden, NIE als Bearer-Header. Damit kann ein in einer URL
    // geleakter Download-Token nicht für reguläre API-Aufrufe missbraucht
    // werden (Pentest Runde 7 – NIEDRIG, Token-in-URL-Defense).
    if (decoded.type === 'download' && tokenSource !== 'query') {
      res.status(401).json({ success: false, error: 'Falscher Token-Typ' });
      return;
    }
    if (decoded.type && decoded.type !== 'access' && decoded.type !== 'download') {
      res.status(401).json({ success: false, error: 'Falscher Token-Typ' });
      return;
    }

    // Prüfen ob Token durch Rechteänderung/Passwort-Reset invalidiert wurde
    if (decoded.userId && decoded.iat) {
      // Mitarbeiter-Login
      const user = await prisma.user.findUnique({
        where: { id: decoded.userId },
        select: { tokenInvalidatedAt: true, isActive: true },
      });

      if (!user || !user.isActive) {
        res.status(401).json({ success: false, error: 'Benutzer nicht mehr aktiv' });
        return;
      }

      if (user.tokenInvalidatedAt) {
        const tokenIssuedAt = decoded.iat * 1000;
        if (tokenIssuedAt < user.tokenInvalidatedAt.getTime()) {
          res.status(401).json({
            success: false,
            error: 'Ihre Berechtigungen wurden geändert. Bitte melden Sie sich erneut an.',
          });
          return;
        }
      }
    } else if (decoded.isCustomerPortal && decoded.customerId && decoded.iat) {
      // Portal-Kunden-Login: gleiche Prüfung
      const customer = await prisma.customer.findUnique({
        where: { id: decoded.customerId },
        select: { portalTokenInvalidatedAt: true, portalEnabled: true },
      });

      if (!customer || !customer.portalEnabled) {
        res.status(401).json({ success: false, error: 'Portal-Zugang nicht mehr aktiv' });
        return;
      }

      if (customer.portalTokenInvalidatedAt) {
        const tokenIssuedAt = decoded.iat * 1000;
        if (tokenIssuedAt < customer.portalTokenInvalidatedAt.getTime()) {
          res.status(401).json({
            success: false,
            error: 'Ihre Sitzung ist ungültig. Bitte melden Sie sich erneut an.',
          });
          return;
        }
      }
    }

    req.user = decoded;
    next();
  } catch (err) {
    // JWT-Failures sind interessant: alg=none, manipulierte Signature,
    // expired Token. Emit SecurityEvent (asynchron, blockt nicht).
    emitSecurityEvent({
      type: 'TOKEN_REJECTED',
      severity: err instanceof jwt.TokenExpiredError ? 'LOW' : 'HIGH',
      message: err instanceof Error ? `JWT abgelehnt: ${err.message}` : 'JWT abgelehnt',
      ipAddress: req.ip || (req.socket as any)?.remoteAddress || 'unknown',
      endpoint: `${req.method} ${req.path}`,
    });
    res.status(401).json({ success: false, error: 'Ungültiger Token' });
  }
}

export function requirePermission(...requiredPermissions: string[]) {
  return (req: AuthRequest, res: Response, next: NextFunction): void => {
    if (!req.user) {
      res.status(401).json({ success: false, error: 'Nicht authentifiziert' });
      return;
    }

    const userPermissions = req.user.permissions || [];

    // Check if user has any of the required permissions
    const hasPermission = requiredPermissions.some((perm) =>
      userPermissions.includes(perm)
    );

    if (!hasPermission) {
      res.status(403).json({
        success: false,
        error: 'Keine Berechtigung für diese Aktion',
      });
      return;
    }

    next();
  };
}

// Middleware to check if user can access specific customer data
export function requireCustomerAccess(
  req: AuthRequest,
  res: Response,
  next: NextFunction
): void {
  if (!req.user) {
    res.status(401).json({ success: false, error: 'Nicht authentifiziert' });
    return;
  }

  // WICHTIG: erst die isCustomerPortal-Prüfung, DANN erst die Perm-Prüfung.
  // Portal-User bekommen `customers:read` im JWT (für eigene Daten); ohne
  // den Portal-Check vorne weg short-circuited die alte Logik auf der
  // Perm und ließ Portal-User auf fremde customerId zugreifen.
  // Pentest 2026-05-24 (MEDIUM 31.2 IDOR auf /api/customers/:id/
  // stressfrei-emails). Auch andere Routes mit dem gleichen Middleware-
  // Pattern wären betroffen gewesen.
  const userPermissions = req.user.permissions || [];
  const isPortal = !!(req.user as any).isCustomerPortal;
  const customerId = parseInt(req.params.customerId || req.params.id);

  if (isPortal) {
    const allowedIds = [
      req.user.customerId,
      ...((req.user as any).representedCustomerIds || []),
    ].filter(Boolean);
    if (allowedIds.includes(customerId)) {
      next();
      return;
    }
    res.status(403).json({
      success: false,
      error: 'Kein Zugriff auf diese Kundendaten',
    });
    return;
  }

  // Mitarbeiter/Admin: customers:read oder customers:update reicht
  if (
    userPermissions.includes('customers:read') ||
    userPermissions.includes('customers:update')
  ) {
    next();
    return;
  }

  res.status(403).json({
    success: false,
    error: 'Kein Zugriff auf diese Kundendaten',
  });
}
