"use server";

import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { requireSession } from "@/lib/auth/session";
import { getViewerScope, visibleWhere } from "./queries";
import { isValidPin, openSecret, sealSecret, PIN_MAX_LENGTH, PIN_MIN_LENGTH } from "./crypto";

// ──────────────────────────────────────────────────────────────────────────
// Schreibzugriffe & Entschlüsselung des Passwortmanagers.
// Jeder Zugriff auf ein Geheimnis wird im Audit-Log vermerkt.
// ──────────────────────────────────────────────────────────────────────────

const MAX_SECRET = 512;
const MAX_PIN_ATTEMPTS = 5;
const PIN_LOCK_MS = 5 * 60 * 1000;

export type VaultResult = { ok?: true; id?: string; error?: string };

export type VaultInput = {
  title: string;
  username?: string | null;
  url?: string | null;
  category?: string | null;
  notes?: string | null;
  /** Neues Geheimnis; beim Bearbeiten leer lassen, um es unverändert zu lassen. */
  secret?: string | null;
  /** "keep" nur beim Bearbeiten sinnvoll. */
  pinMode?: "none" | "set" | "keep";
  pin?: string | null;
  pinHint?: string | null;
  /** Aktuelle PIN — nötig, um den PIN-Schutz ohne neues Passwort zu ändern. */
  currentPin?: string | null;
};

function clean(v: string | null | undefined, max: number): string | null {
  const s = (v ?? "").trim();
  return s ? s.slice(0, max) : null;
}

async function audit(userId: string, action: string, entryId: string) {
  await prisma.auditLog
    .create({ data: { userId, action, entity: "VaultEntry", entityId: entryId } })
    .catch(() => {});
}

/** Lädt einen Eintrag inklusive Zugriffsrechten des angemeldeten Nutzers. */
async function loadForUser(entryId: string) {
  const session = await requireSession();
  const userId = session.user.id;
  const perms = session.user.permissions ?? [];
  const scope = await getViewerScope(userId);

  const entry = await prisma.vaultEntry.findUnique({
    where: { id: entryId },
    select: {
      id: true,
      ownerId: true,
      secret: true,
      pinProtected: true,
      failedPinAttempts: true,
      pinLockedUntil: true,
      shares: {
        select: { canEdit: true, userId: true, teamId: true, roleId: true },
      },
    },
  });
  if (!entry) return { error: "Eintrag nicht gefunden." as const };

  const isOwner = entry.ownerId === userId;
  const viewAll = perms.includes("vault.view_all");
  const mine = entry.shares.filter(
    (s) =>
      s.userId === userId ||
      (s.teamId && scope.teamIds.includes(s.teamId)) ||
      (s.roleId && scope.roleIds.includes(s.roleId)),
  );

  const canView = isOwner || viewAll || mine.length > 0;
  if (!canView) return { error: "Kein Zugriff auf diesen Eintrag." as const };

  return {
    session,
    userId,
    entry,
    isOwner,
    canEdit: perms.includes("vault.manage") && (isOwner || mine.some((s) => s.canEdit)),
    canDelete: isOwner || viewAll,
    canShare: perms.includes("vault.share") && isOwner,
  };
}

// ── Anlegen ───────────────────────────────────────────────────────────────

export async function createVaultEntryAction(input: VaultInput): Promise<VaultResult> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("vault.manage")) {
    return { error: "Keine Berechtigung, Zugangsdaten anzulegen." };
  }

  const title = clean(input.title, 160);
  if (!title) return { error: "Titel darf nicht leer sein." };

  const secret = (input.secret ?? "").trim();
  if (!secret) return { error: "Passwort darf nicht leer sein." };
  if (secret.length > MAX_SECRET) return { error: "Passwort ist zu lang." };

  const usePin = input.pinMode === "set";
  if (usePin && !isValidPin(input.pin ?? "")) {
    return { error: `PIN muss aus ${PIN_MIN_LENGTH}–${PIN_MAX_LENGTH} Ziffern bestehen.` };
  }

  const entry = await prisma.vaultEntry.create({
    data: {
      title,
      username: clean(input.username, 190),
      url: clean(input.url, 500),
      category: clean(input.category, 60),
      notes: clean(input.notes, 4000),
      secret: sealSecret(secret, usePin ? input.pin : null),
      pinProtected: usePin,
      pinLength: usePin ? (input.pin ?? "").length : null,
      pinHint: usePin ? clean(input.pinHint, 80) : null,
      ownerId: session.user.id,
    },
    select: { id: true },
  });

  await audit(session.user.id, "vault.create", entry.id);
  revalidatePath("/vault");
  return { ok: true, id: entry.id };
}

// ── Bearbeiten ────────────────────────────────────────────────────────────

export async function updateVaultEntryAction(
  entryId: string,
  input: VaultInput,
): Promise<VaultResult> {
  const ctx = await loadForUser(entryId);
  if (ctx.error) return { error: ctx.error };
  if (!ctx.canEdit) return { error: "Keine Berechtigung, diesen Eintrag zu bearbeiten." };

  const title = clean(input.title, 160);
  if (!title) return { error: "Titel darf nicht leer sein." };

  const newSecret = (input.secret ?? "").trim();
  if (newSecret.length > MAX_SECRET) return { error: "Passwort ist zu lang." };

  const mode = input.pinMode ?? "keep";
  const wantsPin = mode === "set";
  if (wantsPin && !isValidPin(input.pin ?? "")) {
    return { error: `PIN muss aus ${PIN_MIN_LENGTH}–${PIN_MAX_LENGTH} Ziffern bestehen.` };
  }

  const data: Record<string, unknown> = {
    title,
    username: clean(input.username, 190),
    url: clean(input.url, 500),
    category: clean(input.category, 60),
    notes: clean(input.notes, 4000),
  };

  const pinChanges = mode !== "keep" || wantsPin;

  if (newSecret) {
    // Neues Passwort → einfach frisch versiegeln, alte PIN wird nicht gebraucht.
    data.secret = sealSecret(newSecret, wantsPin ? input.pin : null);
    data.pinProtected = wantsPin;
    data.pinLength = wantsPin ? (input.pin ?? "").length : null;
    data.pinHint = wantsPin ? clean(input.pinHint, 80) : null;
    data.rotatedAt = new Date();
    data.failedPinAttempts = 0;
    data.pinLockedUntil = null;
  } else if (pinChanges) {
    // Nur der PIN-Schutz ändert sich → altes Geheimnis muss geöffnet werden.
    const opened = openSecret(ctx.entry.secret, input.currentPin ?? null);
    if (!opened.ok) {
      return {
        error:
          opened.reason === "PIN_REQUIRED"
            ? "Zum Ändern des PIN-Schutzes wird die aktuelle PIN benötigt."
            : opened.reason === "PIN_WRONG"
              ? "Aktuelle PIN ist falsch."
              : "Eintrag konnte nicht entschlüsselt werden.",
      };
    }
    data.secret = sealSecret(opened.value, wantsPin ? input.pin : null);
    data.pinProtected = wantsPin;
    data.pinLength = wantsPin ? (input.pin ?? "").length : null;
    data.pinHint = wantsPin ? clean(input.pinHint, 80) : null;
    data.failedPinAttempts = 0;
    data.pinLockedUntil = null;
  } else if (ctx.entry.pinProtected) {
    // Hinweistext darf auch ohne PIN angepasst werden.
    data.pinHint = clean(input.pinHint, 80);
  }

  await prisma.vaultEntry.update({ where: { id: entryId }, data });
  await audit(ctx.userId, "vault.update", entryId);
  revalidatePath("/vault");
  return { ok: true, id: entryId };
}

// ── Löschen ───────────────────────────────────────────────────────────────

export async function deleteVaultEntryAction(entryId: string): Promise<VaultResult> {
  const ctx = await loadForUser(entryId);
  if (ctx.error) return { error: ctx.error };
  if (!ctx.canDelete) return { error: "Nur die Besitzer:in darf diesen Eintrag löschen." };

  await prisma.vaultEntry.delete({ where: { id: entryId } });
  await audit(ctx.userId, "vault.delete", entryId);
  revalidatePath("/vault");
  return { ok: true };
}

// ── Freigaben ─────────────────────────────────────────────────────────────

export type ShareInput = {
  userIds: string[];
  teamIds: string[];
  roleIds: string[];
  canEdit: boolean;
};

export async function setVaultSharesAction(
  entryId: string,
  input: ShareInput,
): Promise<VaultResult> {
  const ctx = await loadForUser(entryId);
  if (ctx.error) return { error: ctx.error };
  if (!ctx.canShare) {
    return { error: "Nur die Besitzer:in mit Freigaberecht darf Zugriffe vergeben." };
  }

  // Nur existierende Ziele übernehmen; die eigene Person braucht keine Freigabe.
  const [users, teams, roles] = await Promise.all([
    prisma.user.findMany({
      where: { id: { in: input.userIds.filter((id) => id !== ctx.entry.ownerId) } },
      select: { id: true },
    }),
    prisma.team.findMany({ where: { id: { in: input.teamIds } }, select: { id: true } }),
    prisma.role.findMany({ where: { id: { in: input.roleIds } }, select: { id: true } }),
  ]);

  await prisma.$transaction([
    prisma.vaultShare.deleteMany({ where: { entryId } }),
    prisma.vaultShare.createMany({
      data: [
        ...users.map((u) => ({ entryId, userId: u.id, canEdit: input.canEdit })),
        ...teams.map((t) => ({ entryId, teamId: t.id, canEdit: input.canEdit })),
        ...roles.map((r) => ({ entryId, roleId: r.id, canEdit: input.canEdit })),
      ],
    }),
  ]);

  await audit(ctx.userId, "vault.share", entryId);
  revalidatePath("/vault");
  return { ok: true };
}

// ── Geheimnis anzeigen ────────────────────────────────────────────────────

export type RevealResult =
  | { ok: true; value: string }
  | { ok: false; error: string; needsPin?: boolean; lockedUntil?: string };

export async function revealSecretAction(
  entryId: string,
  pin?: string | null,
): Promise<RevealResult> {
  const ctx = await loadForUser(entryId);
  if (ctx.error) return { ok: false, error: ctx.error };

  const { entry } = ctx;

  if (entry.pinLockedUntil && entry.pinLockedUntil > new Date()) {
    return {
      ok: false,
      error: "Zu viele Fehlversuche — der Eintrag ist vorübergehend gesperrt.",
      needsPin: true,
      lockedUntil: entry.pinLockedUntil.toISOString(),
    };
  }

  const opened = openSecret(entry.secret, pin ?? null);

  if (opened.ok) {
    if (entry.failedPinAttempts > 0 || entry.pinLockedUntil) {
      await prisma.vaultEntry.update({
        where: { id: entryId },
        data: { failedPinAttempts: 0, pinLockedUntil: null },
      });
    }
    await audit(ctx.userId, "vault.reveal", entryId);
    return { ok: true, value: opened.value };
  }

  if (opened.reason === "PIN_REQUIRED") {
    return { ok: false, error: "Dieser Eintrag ist mit einer PIN geschützt.", needsPin: true };
  }
  if (opened.reason === "CORRUPT") {
    return {
      ok: false,
      error:
        "Eintrag konnte nicht entschlüsselt werden — vermutlich hat sich der Server-Schlüssel geändert.",
    };
  }

  // Falsche PIN → Fehlversuch zählen und ggf. sperren.
  const attempts = entry.failedPinAttempts + 1;
  const locked = attempts >= MAX_PIN_ATTEMPTS;
  await prisma.vaultEntry.update({
    where: { id: entryId },
    data: {
      failedPinAttempts: locked ? 0 : attempts,
      pinLockedUntil: locked ? new Date(Date.now() + PIN_LOCK_MS) : null,
    },
  });
  await audit(ctx.userId, "vault.pin_failed", entryId);

  return {
    ok: false,
    needsPin: true,
    error: locked
      ? "Zu viele Fehlversuche — der Eintrag ist für 5 Minuten gesperrt."
      : `PIN ist falsch. Noch ${MAX_PIN_ATTEMPTS - attempts} Versuch(e).`,
    ...(locked ? { lockedUntil: new Date(Date.now() + PIN_LOCK_MS).toISOString() } : {}),
  };
}

/** Bequemer Zähler fürs Dashboard/Modul-Badge. */
export async function countVaultEntriesAction(): Promise<number> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("vault.view")) return 0;
  const scope = await getViewerScope(session.user.id);
  return prisma.vaultEntry.count({
    where: visibleWhere(
      session.user.id,
      scope,
      Boolean(session.user.permissions?.includes("vault.view_all")),
    ),
  });
}
