// src/lib/mcp/keys-actions.ts — MCP API key + add-on entitlement server actions.
"use server";
import { randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import { revalidatePath, updateTag } from "next/cache";
import { getActiveOrgId, getActiveUserId } from "@/lib/active-org";
import { auth } from "@/lib/auth";
import { isImpersonating } from "@/lib/auth/impersonation-guard";
import { requestHeaders } from "@/lib/auth/request-headers";
import {
  assertSuperAdmin,
  isCurrentUserSuperAdmin,
} from "@/lib/auth/super-admin";
import { db } from "@/lib/db/client";
import { mcpApiKeys, orgAddons } from "@/lib/db/domain-schema";
import { isAddonEnabled, orgAddonsTag } from "./entitlements";
import { generateApiKey } from "./keys";

const ADDON = "checklist_mcp";
const API_KEYS_PATH = "/settings/api-keys";

/** Throws unless the current user is a member of the active org. Keys are
 *  per-user, so any member may create/list/revoke their OWN keys. */
async function assertActiveMember(): Promise<void> {
  const member = await auth.api.getActiveMember({
    headers: await requestHeaders(),
  });
  if (!member) {
    throw new Error("Forbidden: not a member of the active organization");
  }
}

/** Any member of the active org. The user creates their OWN key: generates it,
 *  persists hash+prefix (never the clear key) stamped with the user id, and
 *  returns the clear key ONCE alongside its id. Requires the add-on enabled. */
export async function createApiKeyAction(
  name: string,
): Promise<{ id: string; key: string }> {
  // En impersonation `userId` EST la cible : la clé lui serait attribuée et
  // consommerait son quota, sans qu'elle l'ait demandée. Blocage explicite
  // (revokeApiKeyAction, elle, reste ouverte : révoquer est un geste de support
  // légitime et l'action est déjà scopée sur l'id de la cible).
  if (await isImpersonating()) {
    throw new Error(
      "Interdit pendant une impersonation : la clé serait attribuée à l'utilisateur inspecté",
    );
  }
  const [orgId, userId] = await Promise.all([
    getActiveOrgId(),
    getActiveUserId(),
  ]);
  await assertActiveMember();
  if (!(await isAddonEnabled(orgId, ADDON))) {
    throw new Error("Add-on non activé pour cette organisation");
  }
  const trimmed = name.trim();
  if (!trimmed) throw new Error("Un nom est requis");

  const { key, prefix, hash } = generateApiKey();
  const id = randomUUID();
  await db.insert(mcpApiKeys).values({
    id,
    orgId,
    userId,
    name: trimmed,
    prefix,
    keyHash: hash,
  });
  revalidatePath(API_KEYS_PATH);
  return { id, key };
}

/** The current user's OWN keys in the active org. Never exposes the hash or the
 *  clear key. */
export async function listApiKeysAction(): Promise<
  {
    id: string;
    name: string;
    prefix: string;
    createdAt: Date;
    lastUsedAt: Date | null;
    revokedAt: Date | null;
    requestCount: number;
  }[]
> {
  const [orgId, userId] = await Promise.all([
    getActiveOrgId(),
    getActiveUserId(),
  ]);
  await assertActiveMember();
  return db
    .select({
      id: mcpApiKeys.id,
      name: mcpApiKeys.name,
      prefix: mcpApiKeys.prefix,
      createdAt: mcpApiKeys.createdAt,
      lastUsedAt: mcpApiKeys.lastUsedAt,
      revokedAt: mcpApiKeys.revokedAt,
      requestCount: mcpApiKeys.requestCount,
    })
    .from(mcpApiKeys)
    .where(and(eq(mcpApiKeys.orgId, orgId), eq(mcpApiKeys.userId, userId)))
    .orderBy(mcpApiKeys.createdAt);
}

/** Soft-revokes a key (revokedAt = now), scoped to the current user's OWN key
 *  in the active org. A user can only revoke keys they created. */
export async function revokeApiKeyAction(id: string): Promise<void> {
  const [orgId, userId] = await Promise.all([
    getActiveOrgId(),
    getActiveUserId(),
  ]);
  await assertActiveMember();
  await db
    .update(mcpApiKeys)
    .set({ revokedAt: new Date() })
    .where(
      and(
        eq(mcpApiKeys.id, id),
        eq(mcpApiKeys.orgId, orgId),
        eq(mcpApiKeys.userId, userId),
      ),
    );
  revalidatePath(API_KEYS_PATH);
}

/** Super-admin only (god-mode). Enable → upsert the add-on row; disable → delete. */
export async function setOrgAddonAction(
  orgId: string,
  addon: string,
  enabled: boolean,
): Promise<void> {
  await assertSuperAdmin();
  if (enabled) {
    await db
      .insert(orgAddons)
      .values({ orgId, addon })
      .onConflictDoNothing({ target: [orgAddons.orgId, orgAddons.addon] });
  } else {
    await db
      .delete(orgAddons)
      .where(and(eq(orgAddons.orgId, orgId), eq(orgAddons.addon, addon)));
  }
  revalidatePath(API_KEYS_PATH);
  // isAddonEnabled (entitlements.ts) is unstable_cache'd 30s per (org, addon) —
  // invalidate immediately so the god-mode toggle takes effect on the very
  // next MCP request instead of waiting out the TTL (QW2-A). Server Action →
  // updateTag (immediate, read-your-own-writes), not revalidateTag.
  updateTag(orgAddonsTag(orgId));
}

/** Page helper: is the `checklist_mcp` add-on enabled for the active org, and is
 *  the current user a super-admin (to surface the god-mode toggle)? */
export async function getAddonStateAction(): Promise<{
  enabled: boolean;
  isSuperAdmin: boolean;
}> {
  const orgId = await getActiveOrgId();
  const [enabled, superAdmin] = await Promise.all([
    isAddonEnabled(orgId, ADDON),
    isCurrentUserSuperAdmin(),
  ]);
  return { enabled, isSuperAdmin: superAdmin };
}
