import { Response } from 'express';
import prisma from '../lib/prisma.js';
import { AuthRequest, ApiResponse } from '../types/index.js';
import { loginRateLimiter, passwordResetRateLimiter } from '../middleware/rateLimit.js';
import { logChange } from '../services/audit.service.js';

const LOGIN_WINDOW_MS = 15 * 60 * 1000;

type ActiveLock = {
  ipAddress: string;
  email: string | null;    // null = Passwort-Reset oder Login ohne Email
  lastHit: Date;
  hitCount: number;
  lastEndpoint: string | null;
  limiters: string[];       // 'login' / 'password-reset'
};

function lockKey(ip: string, email: string | null): string {
  return `${ip}|${(email || '').toLowerCase()}`;
}

/**
 * Listet aktive Sperren als (IP, Email)-Tupel. Jedes Tupel ist ein eigener
 * Bucket im Limiter – Reset gilt exakt für dieses Paar.
 */
export async function getActiveRateLimits(req: AuthRequest, res: Response): Promise<void> {
  try {
    const since = new Date(Date.now() - LOGIN_WINDOW_MS);
    const events = await prisma.securityEvent.findMany({
      where: { type: 'RATE_LIMIT_HIT', createdAt: { gte: since } },
      orderBy: { createdAt: 'desc' },
      select: {
        ipAddress: true,
        userEmail: true,
        endpoint: true,
        createdAt: true,
        details: true,
      },
    });

    const byKey = new Map<string, ActiveLock>();
    for (const ev of events) {
      const ip = ev.ipAddress || 'unknown';
      const email = (ev.userEmail || '').toLowerCase() || null;
      const limiter = (ev.details as any)?.limiter ?? 'unknown';
      const key = lockKey(ip, email);
      const existing = byKey.get(key);
      if (existing) {
        existing.hitCount += 1;
        if (!existing.limiters.includes(limiter)) existing.limiters.push(limiter);
      } else {
        byKey.set(key, {
          ipAddress: ip,
          email,
          lastHit: ev.createdAt,
          hitCount: 1,
          lastEndpoint: ev.endpoint,
          limiters: [limiter],
        });
      }
    }

    // Bereits manuell freigegebene aus der Liste werfen. Reset-Audit-Logs
    // nutzen resourceId = "<ip>|<email>" (gleicher Schlüssel wie Bucket).
    const candidates = Array.from(byKey.entries()).map(([k, e]) => ({
      mapKey: k,
      resourceId: k,
      lastHit: e.lastHit,
    }));
    if (candidates.length > 0) {
      const recentResets = await prisma.auditLog.findMany({
        where: {
          resourceType: 'RateLimit',
          resourceId: { in: candidates.map((c) => c.resourceId) },
          createdAt: { gte: since },
        },
        select: { resourceId: true, createdAt: true },
        orderBy: { createdAt: 'desc' },
      });
      const resetMap = new Map<string, Date>();
      for (const r of recentResets) {
        if (r.resourceId && !resetMap.has(r.resourceId)) resetMap.set(r.resourceId, r.createdAt);
      }
      for (const c of candidates) {
        const reset = resetMap.get(c.resourceId);
        if (reset && reset >= c.lastHit) byKey.delete(c.mapKey);
      }
    }

    const list = Array.from(byKey.values()).sort(
      (a, b) => b.lastHit.getTime() - a.lastHit.getTime(),
    );
    res.json({ success: true, data: list } as ApiResponse);
  } catch (error) {
    console.error('getActiveRateLimits error:', error);
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden der aktiven Rate-Limits',
    } as ApiResponse);
  }
}

/**
 * Reset für ein konkretes (IP, Email)-Tupel. Body MUSS ipAddress enthalten
 * + optional email. Bei fehlender Email wird `<ip>|<no-email>` reseted
 * (für Login-Versuche mit leerem Body). Für Passwort-Reset-Limit wird der
 * IP-only-Key (alter Stil) zusätzlich reseted.
 */
export async function resetRateLimit(req: AuthRequest, res: Response): Promise<void> {
  try {
    const ip = (req.body?.ipAddress || '').toString().trim();
    const email = (req.body?.email || '').toString().trim().toLowerCase();

    if (!ip) {
      res.status(400).json({
        success: false,
        error: 'IP-Adresse erforderlich',
      } as ApiResponse);
      return;
    }

    // Login-Tuple-Bucket: `${ip}|${email}` bzw. `${ip}|<no-email>`
    const loginKey = email ? `${ip}|${email}` : `${ip}|<no-email>`;
    await (loginRateLimiter as any).resetKey?.(loginKey);

    // Passwort-Reset-Limit ist (noch) IP-only – auch zurücksetzen
    await (passwordResetRateLimiter as any).resetKey?.(ip);

    // Audit-Resource-ID = der Bucket-Key, damit getActiveRateLimits den
    // Eintrag aus der Anzeige filtern kann.
    const audited = `${ip}|${email || ''}`;
    await logChange({
      req,
      action: 'UPDATE',
      resourceType: 'RateLimit',
      resourceId: audited,
      label: email
        ? `Rate-Limit für (IP ${ip}, Email ${email}) manuell freigegeben`
        : `Rate-Limit für IP ${ip} manuell freigegeben`,
    });

    res.json({
      success: true,
      message: email
        ? `Rate-Limit für (${ip}, ${email}) freigegeben`
        : `Rate-Limit für ${ip} freigegeben`,
    } as ApiResponse);
  } catch (error) {
    console.error('resetRateLimit error:', error);
    res.status(500).json({
      success: false,
      error: 'Fehler beim Zurücksetzen des Rate-Limits',
    } as ApiResponse);
  }
}
