import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import prisma from '../lib/prisma.js';
import * as providerService from '../services/provider.service.js';
import { logChange } from '../services/audit.service.js';
import { ApiResponse, AuthRequest } from '../types/index.js';

// Pentest 47.1 (MEDIUM, 2026-06-01): Open Redirect / Phishing via
// provider.portalUrl. Bei kompromittiertem Admin-Account konnte ein
// Angreifer einen Phishing-Link auf einem real existierenden Provider
// hinterlegen – jeder Portal-User mit dem Provider sah ihn dauerhaft.
// Re-Auth-Pattern analog 47.3 (Staff-Password): bei Änderung der
// portalUrl-Domain muss der Admin sein eigenes Passwort mitsenden.
async function requireCallerPasswordReAuth(req: AuthRequest, providedPassword: unknown): Promise<{ ok: true } | { ok: false; status: number; error: string }> {
  const callerId = req.user?.userId;
  if (!callerId) return { ok: false, status: 401, error: 'Nicht authentifiziert' };
  if (typeof providedPassword !== 'string' || providedPassword.length === 0) {
    return { ok: false, status: 400, error: 'Bitte das eigene Passwort zur Bestätigung mitsenden.' };
  }
  const caller = await prisma.user.findUnique({ where: { id: callerId }, select: { password: true } });
  // Timing-Schutz: immer einen bcrypt.compare laufen lassen
  const callerHash = caller?.password ?? '$2a$10$0000000000000000000000000000000000000000000000000000';
  const ok = await bcrypt.compare(providedPassword, callerHash);
  if (!caller || !ok) return { ok: false, status: 403, error: 'Eigenes Passwort ist falsch.' };
  return { ok: true };
}

// Pentest 49.1 (LOW, 2026-06-01): nur Host-Vergleich ließ Pfad-Änderungen
// am gleichen Host (z.B. `https://1und1.de/neue/pfad`) ohne Re-Auth
// durchgehen – ein gestohlener JWT konnte Phishing-Pfade auf trusted
// Domains plazieren. Jetzt vergleichen wir die komplette normalisierte
// URL (Trailing-Slash, Whitespace).
function normalizeUrlForCompare(url: string | null | undefined): string {
  return (url ?? '').trim().replace(/\/+$/, '').toLowerCase();
}

export async function getProviders(req: Request, res: Response): Promise<void> {
  try {
    const includeInactive = req.query.includeInactive === 'true';
    const providers = await providerService.getAllProviders(includeInactive);
    res.json({ success: true, data: providers } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden der Anbieter',
    } as ApiResponse);
  }
}

export async function getProvider(req: Request, res: Response): Promise<void> {
  try {
    // `req.params.id` ist Pfad-Segment – bei /api/providers/email landet
    // hier der String "email", den parseInt zu NaN macht. Ohne Validierung
    // fuhr Prisma dann gegen `WHERE id = NaN` und warf 500.
    // Pentest 2026-05-20, 29.5: explizit 404 statt 500. Andere Sub-Routes
    // wie /api/providers/<id>/tariffs greifen weiter wie gehabt.
    const id = parseInt(req.params.id, 10);
    if (!Number.isFinite(id) || id < 1) {
      res.status(404).json({
        success: false,
        error: 'Anbieter nicht gefunden',
      } as ApiResponse);
      return;
    }
    const provider = await providerService.getProviderById(id);
    if (!provider) {
      res.status(404).json({
        success: false,
        error: 'Anbieter nicht gefunden',
      } as ApiResponse);
      return;
    }
    res.json({ success: true, data: provider } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden des Anbieters',
    } as ApiResponse);
  }
}

export async function createProvider(req: Request, res: Response): Promise<void> {
  try {
    const { currentPassword, ...providerData } = req.body || {};
    // 47.1: Beim Create mit portalUrl ist Re-Auth Pflicht. Ohne portalUrl
    // (rein interner Provider-Stammdatensatz) kein Zwang.
    if (providerData.portalUrl) {
      const reauth = await requireCallerPasswordReAuth(req as AuthRequest, currentPassword);
      if (!reauth.ok) {
        res.status(reauth.status).json({ success: false, error: reauth.error } as ApiResponse);
        return;
      }
    }
    const provider = await providerService.createProvider(providerData);
    await logChange({
      req, action: 'CREATE', resourceType: 'Provider',
      resourceId: provider.id.toString(),
      label: `Anbieter ${provider.name} angelegt${provider.portalUrl ? ` mit Portal-URL ${provider.portalUrl}` : ''}`,
    });
    res.status(201).json({ success: true, data: provider } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Anbieters',
    } as ApiResponse);
  }
}

export async function updateProvider(req: Request, res: Response): Promise<void> {
  try {
    const providerId = parseInt(req.params.id);
    const { currentPassword, ...providerData } = req.body || {};

    // 47.1 + 49.1: jede portalUrl-Änderung braucht Re-Auth – inkl. reiner
    // Pfad-Änderungen am gleichen Host (Phishing-Pfade auf trusted Domain).
    // Reine Namens-/Tarif-Edits bleiben friction-frei.
    if (providerData.portalUrl !== undefined) {
      const before = await providerService.getProviderById(providerId);
      const isUrlChange = normalizeUrlForCompare(before?.portalUrl) !== normalizeUrlForCompare(providerData.portalUrl);
      if (isUrlChange) {
        const reauth = await requireCallerPasswordReAuth(req as AuthRequest, currentPassword);
        if (!reauth.ok) {
          res.status(reauth.status).json({ success: false, error: reauth.error } as ApiResponse);
          return;
        }
      }
    }

    const provider = await providerService.updateProvider(providerId, providerData);
    await logChange({
      req, action: 'UPDATE', resourceType: 'Provider',
      resourceId: provider.id.toString(),
      label: providerData.portalUrl !== undefined
        ? `Anbieter ${provider.name} aktualisiert (Portal-URL: ${provider.portalUrl ?? 'entfernt'})`
        : `Anbieter ${provider.name} aktualisiert`,
    });
    res.json({ success: true, data: provider } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Anbieters',
    } as ApiResponse);
  }
}

export async function deleteProvider(req: Request, res: Response): Promise<void> {
  try {
    const providerId = parseInt(req.params.id);
    const provider = await providerService.getProviderById(providerId);
    await providerService.deleteProvider(providerId);
    await logChange({
      req, action: 'DELETE', resourceType: 'Provider',
      resourceId: providerId.toString(),
      label: `Anbieter ${provider?.name || providerId} gelöscht`,
    });
    res.json({ success: true, message: 'Anbieter gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen des Anbieters',
    } as ApiResponse);
  }
}
