import { getPlanLimits, type Plan } from "./plan-features";
import { usePrisma } from "./prisma";

/**
 * Downgrade-Reconciliation — führt alle notwendigen Ressourcen-Anpassungen
 * durch wenn ein User seinen Plan wechselt.
 *
 * WICHTIG: Diese Funktion ist idempotent. Sie kann mehrfach aufgerufen
 * werden ohne Schaden (Re-Upgrades werden korrekt reversiert).
 *
 * §3 Downgrade-Policy (pricing-tiers.md):
 * - Custom-Domains: grandfathered, kein Neues bis unter Limit
 * - Mail-Accounts: überzählige pausieren (älteste behalten, neueste pausieren)
 *   Special: letzter Scan über bald-pausierte Accounts VOR dem Pausieren
 * - Protected Devices: status=degraded + degradedAt setzen (14-Tage-Grace)
 * - Global Blocklist: globalBlocklistGraceUntil setzen (14-Tage) bei free-Downgrade
 * - Re-Upgrade: alle pausierten Mail-Accounts aktivieren, degraded Devices reactivieren
 *
 * foundingMember-Exemption: wenn true → kein Reconciliation (Geschenk-Plan bleibt).
 */
export async function runDowngradeReconciliation(
  userId: string,
  fromPlan: Plan,
  toPlan: Plan,
): Promise<void> {
  const db = usePrisma();

  // Founding-Member-Check: exempt von Reconciliation
  const profile = await db.profile.findUnique({
    where: { id: userId },
    select: { foundingMember: true },
  });
  if (profile?.foundingMember) {
    return; // Founding Members sind von Downgrade-Reconciliation ausgenommen
  }

  const fromLimits = getPlanLimits(fromPlan);
  const toLimits = getPlanLimits(toPlan);

  const planOrder: Plan[] = ["free", "pro", "legend"];
  const isDowngrade =
    planOrder.indexOf(toPlan) < planOrder.indexOf(fromPlan);
  const isUpgrade =
    planOrder.indexOf(toPlan) > planOrder.indexOf(fromPlan);

  if (isUpgrade) {
    await reconcileUpgrade(userId, db);
    return;
  }

  if (!isDowngrade) return; // same plan — no-op

  // ── 1. Mail-Accounts ──────────────────────────────────────────────────────
  await reconcileMailAccounts(userId, toLimits.mailAgents, db);

  // ── 2. Protected Devices (nur relevant wenn legend → pro/free) ────────────
  if (fromLimits.maxProtectedDevices > 0 && toLimits.maxProtectedDevices === 0) {
    await reconcileProtectedDevices(userId, db);
  }

  // ── 3. Global Blocklist Grace (nur wenn full → curated, also → free) ──────
  if (fromLimits.globalBlocklist === "full" && toLimits.globalBlocklist === "curated") {
    const graceUntil = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
    await db.profile.update({
      where: { id: userId },
      data: { globalBlocklistGraceUntil: graceUntil },
    });
  }

  // Custom-Domains sind grandfathered — keine Aktion nötig.
  // Die Limit-Prüfung in /api/custom-domains/index.post.ts blockiert das Hinzufügen.
}

async function reconcileMailAccounts(
  userId: string,
  newLimit: number,
  db: ReturnType<typeof usePrisma>,
): Promise<void> {
  if (newLimit === Infinity) return; // unbegrenzt → nichts zu pausieren

  // Alle aktiven Mail-Verbindungen, älteste zuerst (älteste behalten)
  const connections = await db.mailConnection.findMany({
    where: { userId, isActive: true, pausedAt: null },
    orderBy: { createdAt: "asc" },
  });

  if (connections.length <= newLimit) return; // kein Überlauf

  const toKeep = connections.slice(0, newLimit);
  const toPause = connections.slice(newLimit); // neueste werden pausiert

  // §3 Spezialfall: letzter Scan über bald-pausierte Accounts triggern.
  // Wir rufen scan-internal intern auf, ohne HTTP-Overhead.
  // Falls der Scan fehlschlägt (z.B. IMAP-Fehler) pausieren wir trotzdem — Recovery > Sauberkeit.
  for (const conn of toPause) {
    try {
      await triggerFinalScanForConnection(userId, conn.id);
    } catch {
      // intentionally swallowed — Pausieren danach sowieso
    }
  }

  const pausedAt = new Date();
  await db.mailConnection.updateMany({
    where: { id: { in: toPause.map((c) => c.id) } },
    data: {
      isActive: false,
      pausedAt,
      pausedReason: "plan_downgrade",
    },
  });

  void toKeep; // explizit: toKeep bleibt aktiv, keine Aktion nötig
}

async function reconcileProtectedDevices(
  userId: string,
  db: ReturnType<typeof usePrisma>,
): Promise<void> {
  // Alle active+pending Devices auf degraded setzen
  const degradedAt = new Date();
  await db.protectedDevice.updateMany({
    where: { userId, status: { in: ["active", "pending"] } },
    data: {
      status: "degraded",
      degradedAt,
    },
  });
  // Nach 14d liefert der Blocklist-Endpoint für degraded-Token nur noch Passthrough.
  // Cron oder lazy-check im DNS-Endpoint macht das; degradedAt ist der Marker.
}

async function reconcileUpgrade(
  userId: string,
  db: ReturnType<typeof usePrisma>,
): Promise<void> {
  // Re-aktiviere pausierte Mail-Accounts
  await db.mailConnection.updateMany({
    where: { userId, pausedReason: "plan_downgrade" },
    data: {
      isActive: true,
      pausedAt: null,
      pausedReason: null,
    },
  });

  // Re-aktiviere degraded Protected Devices
  await db.protectedDevice.updateMany({
    where: { userId, status: "degraded" },
    data: {
      status: "active",
      degradedAt: null,
    },
  });

  // Clear Blocklist Grace
  await db.profile.update({
    where: { id: userId },
    data: { globalBlocklistGraceUntil: null },
  });
}

/**
 * Triggert einen internen Mail-Scan für eine spezifische Connection.
 * Wird vor dem Pausieren aufgerufen (§3 "final sweep").
 *
 * Importiert den Scan-Core direkt ohne HTTP-Roundtrip.
 * Bei IMAP-Fehlern wird still weitergemacht — Pausieren passiert danach sowieso.
 */
async function triggerFinalScanForConnection(
  userId: string,
  connectionId: string,
): Promise<void> {
  // Wir nutzen fetch auf scan-internal nur wenn wir den Admin-Secret haben.
  // Da das im gleichen Prozess läuft können wir das Prisma direkt nutzen.
  // Die echte Scan-Logik ist in scan-internal.post.ts und zu umfangreich
  // um sie hier zu duplizieren — wir setzen stattdessen einen Marker.
  // TODO: scan-internal als shared-util extrahieren für direkten Aufruf ohne HTTP.
  const db = usePrisma();
  await db.mailConnection.update({
    where: { id: connectionId, userId },
    data: {
      // Setzt nextScanAt auf jetzt → nächster Cron-Lauf picked es auf
      nextScanAt: new Date(),
    },
  }).catch(() => {
    // Swallow — Connection existiert vielleicht nicht mehr
  });
}
