// src/lib/connectors/store.test.ts — store bas-niveau des connecteurs par org
// (postgres, section gated DB_TESTS=1, même pattern que
// seranking/integration-actions.test.ts). Les fonctions du store opèrent
// directement sur (orgId, provider) : aucune gate ni ping à injecter, donc pas
// de `deps` — les tests tournent contre la vraie DB de test. On vérifie le
// round-trip chiffré (le token stocké n'est jamais en clair) et la sémantique
// active/invalid/absent.
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { eq } from "drizzle-orm";
import { decryptSecret } from "@/lib/connectors/crypto";
import { assertTestDb } from "@/lib/db/assert-test-db";
import { organization } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";
import { orgConnectors } from "@/lib/db/domain-schema";
import {
  getConnectorStatus,
  getConnectorToken,
  markConnectorInvalid,
  removeConnector,
  setConnector,
} from "./store";

const RUN = process.env.DB_TESTS === "1";

describe.skipIf(!RUN)("connectors store (postgres)", () => {
  const ORG = "org-connectors-store-test";
  const USER = "user-connectors-store-test";

  async function cleanup() {
    // FK org_connectors.orgId → organization.id ON DELETE CASCADE : supprimer
    // l'org suffit à faire disparaître ses connecteurs.
    await db.delete(organization).where(eq(organization.id, ORG));
  }

  async function resetOrg() {
    await cleanup();
    await db.insert(organization).values({
      id: ORG,
      name: "connectors-store-test",
      slug: "connectors-store-test",
      createdAt: new Date(),
    });
  }

  /** Lit la ligne brute (colonne chiffrée telle qu'en base). */
  async function readRow() {
    const [row] = await db
      .select({
        encryptedToken: orgConnectors.encryptedToken,
        status: orgConnectors.status,
        connectedBy: orgConnectors.connectedBy,
      })
      .from(orgConnectors)
      .where(eq(orgConnectors.id, `${ORG}-mangools`));
    return row ?? null;
  }

  beforeAll(async () => {
    await assertTestDb();
    await resetOrg();
  });

  afterAll(async () => {
    await cleanup();
  });

  test("setConnector : upsert initial → token chiffré en base, status active", async () => {
    await resetOrg();
    await setConnector(ORG, "mangools", "mangools-token-abc", USER);

    const row = await readRow();
    expect(row).not.toBeNull();
    expect(row?.status).toBe("active");
    expect(row?.connectedBy).toBe(USER);
    // Le token N'EST JAMAIS stocké en clair.
    expect(row?.encryptedToken).not.toBe("mangools-token-abc");
    expect(row?.encryptedToken.startsWith("v1:")).toBe(true);
    // …mais se déchiffre bien vers la valeur d'origine.
    expect(decryptSecret(row?.encryptedToken ?? "")).toBe("mangools-token-abc");
  });

  test("getConnectorToken : renvoie le token déchiffré quand active", async () => {
    await resetOrg();
    await setConnector(ORG, "mangools", "mangools-token-xyz", USER);
    expect(await getConnectorToken(ORG, "mangools")).toBe("mangools-token-xyz");
  });

  test("setConnector : replace → re-chiffre, une seule ligne, re-active", async () => {
    await resetOrg();
    await setConnector(ORG, "mangools", "first-token", USER);
    await markConnectorInvalid(ORG, "mangools");
    await setConnector(ORG, "mangools", "second-token", "user-2");

    // Toujours une seule ligne (upsert, pas d'insert dupliqué).
    const rows = await db
      .select({ id: orgConnectors.id })
      .from(orgConnectors)
      .where(eq(orgConnectors.orgId, ORG));
    expect(rows).toHaveLength(1);

    expect(await getConnectorToken(ORG, "mangools")).toBe("second-token");
    const status = await getConnectorStatus(ORG, "mangools");
    // Re-save repasse en active malgré le markInvalid précédent.
    expect(status).toEqual({ connected: true, status: "active" });
    const row = await readRow();
    expect(row?.connectedBy).toBe("user-2");
  });

  test("markConnectorInvalid : status invalid, getConnectorToken → null", async () => {
    await resetOrg();
    await setConnector(ORG, "mangools", "some-token", USER);
    await markConnectorInvalid(ORG, "mangools");

    expect(await getConnectorToken(ORG, "mangools")).toBeNull();
    expect(await getConnectorStatus(ORG, "mangools")).toEqual({
      connected: false,
      status: "invalid",
    });
  });

  test("getConnectorToken / getConnectorStatus : connecteur absent → null / non connecté", async () => {
    await resetOrg();
    expect(await getConnectorToken(ORG, "mangools")).toBeNull();
    expect(await getConnectorStatus(ORG, "mangools")).toEqual({
      connected: false,
      status: null,
    });
  });

  test("removeConnector : supprime la ligne (retour à l'état absent)", async () => {
    await resetOrg();
    await setConnector(ORG, "mangools", "to-remove", USER);
    await removeConnector(ORG, "mangools");

    expect(await readRow()).toBeNull();
    expect(await getConnectorToken(ORG, "mangools")).toBeNull();
    expect(await getConnectorStatus(ORG, "mangools")).toEqual({
      connected: false,
      status: null,
    });
  });
});
