// src/lib/connectors/store.ts — store bas-niveau du rail de connecteurs par org
// (tokens API BYO chiffrés au repos). `server-only` (PAS "use server") : ce
// module n'est jamais exposé au réseau. Il n'y a AUCUN wrapper "use server"
// ici — les gates (owner/admin), la validation par ping et la résolution de
// l'org active vivent dans la couche action au-dessus. Le store se contente
// d'opérations DB + chiffrement sur un (orgId, provider) déjà résolu, donc pas
// de `deps` injectable : rien d'externe à mocker, les tests tournent contre la
// DB de test.
import "server-only";
import { eq } from "drizzle-orm";
import { decryptSecret, encryptSecret } from "@/lib/connectors/crypto";
import { db } from "@/lib/db/client";
import { orgConnectors } from "@/lib/db/domain-schema";
import type { ConnectorProvider, ConnectorStatus } from "./types";

/** id déterministe d'une ligne connecteur : upsert/lookup par clé primaire. */
function connectorId(orgId: string, provider: ConnectorProvider): string {
  return `${orgId}-${provider}`;
}

/**
 * Token déchiffré du connecteur, ou `null` si absent OU `invalid` — un token
 * marqué invalide ne doit JAMAIS être servi à un appelant (il ferait échouer
 * l'API du provider). Déchiffre à la volée (jamais de token en clair en base).
 */
export async function getConnectorToken(
  orgId: string,
  provider: ConnectorProvider,
): Promise<string | null> {
  const [row] = await db
    .select({
      encryptedToken: orgConnectors.encryptedToken,
      status: orgConnectors.status,
    })
    .from(orgConnectors)
    .where(eq(orgConnectors.id, connectorId(orgId, provider)));
  if (!row || row.status === "invalid") return null;
  return decryptSecret(row.encryptedToken);
}

/**
 * État du connecteur sans jamais exposer le token : absent → non connecté,
 * `active` → connecté, `invalid` → non connecté mais présent (à re-brancher).
 */
export async function getConnectorStatus(
  orgId: string,
  provider: ConnectorProvider,
): Promise<ConnectorStatus> {
  const [row] = await db
    .select({ status: orgConnectors.status })
    .from(orgConnectors)
    .where(eq(orgConnectors.id, connectorId(orgId, provider)));
  if (!row) return { connected: false, status: null };
  return { connected: row.status === "active", status: row.status };
}

/**
 * Branche (ou re-branche) un connecteur : upsert par PK. Chiffre le token,
 * (re)passe le status à `active` — un re-save après un `markConnectorInvalid`
 * réhabilite donc le connecteur. `userId` est enregistré comme dernier
 * connecteur (`connectedBy`) et `updatedAt` est rafraîchi.
 */
export async function setConnector(
  orgId: string,
  provider: ConnectorProvider,
  token: string,
  userId: string,
): Promise<void> {
  const encryptedToken = encryptSecret(token);
  const now = new Date();
  await db
    .insert(orgConnectors)
    .values({
      id: connectorId(orgId, provider),
      orgId,
      provider,
      encryptedToken,
      status: "active",
      connectedBy: userId,
    })
    .onConflictDoUpdate({
      target: orgConnectors.id,
      set: {
        encryptedToken,
        status: "active",
        connectedBy: userId,
        updatedAt: now,
      },
    });
}

/**
 * Marque le connecteur `invalid` (token rejeté par le provider). No-op si le
 * connecteur n'existe pas. Après ça, `getConnectorToken` renvoie `null` — un
 * `setConnector` ultérieur le réhabilite.
 */
export async function markConnectorInvalid(
  orgId: string,
  provider: ConnectorProvider,
): Promise<void> {
  await db
    .update(orgConnectors)
    .set({ status: "invalid", updatedAt: new Date() })
    .where(eq(orgConnectors.id, connectorId(orgId, provider)));
}

/** Débranche un connecteur : supprime la ligne (retour à l'état absent). */
export async function removeConnector(
  orgId: string,
  provider: ConnectorProvider,
): Promise<void> {
  await db
    .delete(orgConnectors)
    .where(eq(orgConnectors.id, connectorId(orgId, provider)));
}
