/**
 * Middleware de autenticação para a External API.
 *
 * Aceita a integration_key via:
 *   - Header: x-nw-key: nw_...
 *
 * Resolve a chave para tenantId e injeta em req.extTenantId.
 * Rate-limiting básico: max 120 req/min por chave (janela deslizante simples em memória).
 */
import type { Request, Response, NextFunction } from 'express'
import type { PrismaClient } from '@prisma/client'

// ─── Cache de API key (evita Prisma query em cada requisição) ────────────────
// Chaves são imutáveis durante a vida da sessão — TTL de 5 min é suficiente.
const KEY_CACHE_TTL = 5 * 60_000
interface KeyCacheEntry { tenantId: string; expiresAt: Date | null; cachedAt: number }
const keyCache = new Map<string, KeyCacheEntry>()

setInterval(() => {
  const now = Date.now()
  for (const [k, v] of keyCache.entries()) {
    if (now - v.cachedAt > KEY_CACHE_TTL) keyCache.delete(k)
  }
}, KEY_CACHE_TTL)

// ─── Rate limiter simples em memória (por chave) ─────────────────────────────
const RATE_WINDOW_MS = 60_000
const RATE_MAX       = 120

interface RateEntry { count: number; resetAt: number }
const rateMap = new Map<string, RateEntry>()

function checkRateLimit(key: string): boolean {
  const now  = Date.now()
  let entry  = rateMap.get(key)
  if (!entry || now > entry.resetAt) {
    entry = { count: 1, resetAt: now + RATE_WINDOW_MS }
    rateMap.set(key, entry)
    return true
  }
  entry.count++
  return entry.count <= RATE_MAX
}

// Limpeza periódica para não vazar memória
setInterval(() => {
  const now = Date.now()
  for (const [k, v] of rateMap.entries()) {
    if (now > v.resetAt) rateMap.delete(k)
  }
}, RATE_WINDOW_MS * 2)

// ─── Extend Express Request ───────────────────────────────────────────────────
declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace Express {
    interface Request {
      extTenantId?: string
    }
  }
}

// ─── Factory ─────────────────────────────────────────────────────────────────
export function buildApiKeyAuth(prisma: PrismaClient) {
  return async function apiKeyAuth(req: Request, res: Response, next: NextFunction) {
    const key = (req.headers['x-nw-key'] as string | undefined)?.trim()

    if (!key) {
      res.status(401).json({ error: 'Header x-nw-key ausente' })
      return
    }

    if (!checkRateLimit(key)) {
      res.status(429).json({ error: 'Rate limit excedido (120 req/min por chave)' })
      return
    }

    // Verifica cache antes de ir ao banco
    const cached = keyCache.get(key)
    if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
      if (cached.expiresAt && cached.expiresAt < new Date()) {
        res.status(401).json({ error: 'Chave expirada' })
        return
      }
      req.extTenantId = cached.tenantId
      next()
      return
    }

    const apiKey = await prisma.apiKey.findUnique({
      where: { key },
      select: { tenantId: true, isActive: true, expiresAt: true },
    })

    if (!apiKey || !apiKey.isActive) {
      res.status(401).json({ error: 'Chave inválida ou inativa' })
      return
    }

    if (apiKey.expiresAt && apiKey.expiresAt < new Date()) {
      res.status(401).json({ error: 'Chave expirada' })
      return
    }

    keyCache.set(key, { tenantId: apiKey.tenantId, expiresAt: apiKey.expiresAt, cachedAt: Date.now() })
    req.extTenantId = apiKey.tenantId
    next()
  }
}

/**
 * Versão standalone que valida a chave sem passar pelo Express.
 * Usada no handshake do WS (upgrade request).
 */
export async function resolveApiKey(
  prisma: PrismaClient,
  key: string,
): Promise<string | null> {
  if (!key) return null
  const cached = keyCache.get(key)
  if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
    if (cached.expiresAt && cached.expiresAt < new Date()) return null
    return cached.tenantId
  }
  const apiKey = await prisma.apiKey.findUnique({
    where: { key },
    select: { tenantId: true, isActive: true, expiresAt: true },
  })
  if (!apiKey || !apiKey.isActive) return null
  if (apiKey.expiresAt && apiKey.expiresAt < new Date()) return null
  keyCache.set(key, { tenantId: apiKey.tenantId, expiresAt: apiKey.expiresAt, cachedAt: Date.now() })
  return apiKey.tenantId
}
