import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { FeishuApplicationConnectionService } from "../../src/connections/feishuApplicationConnections.js";
import {
  scopedFeishuPrincipalId,
  upsertScopedFeishuIdentity,
  resolveScopedFeishuIdentity,
} from "../../src/feishu/identityNamespace.js";
import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization, testSecretEnvelope } from "./helpers.js";

describe("Feishu connection identity namespace", () => {
  beforeEach(async () => {
    await resetDb();
  });

  afterAll(async () => {
    await prisma.$disconnect();
  });

  it("keeps colliding provider-local user and chat ids isolated by connection", async () => {
    const first = await seedConnection(DEFAULT_ORG_ID, "identity-admin-a", "cli_identity_a");
    await seedTestOrganization("org_identity_other", "identity-other");
    const second = await seedConnection("org_identity_other", "identity-admin-b", "cli_identity_b");

    const [identityA, identityB] = await Promise.all([
      upsertScopedFeishuIdentity(prisma, {
        connectionId: first.id,
        openId: "ou_collision",
        displayName: "Teacher A",
      }),
      upsertScopedFeishuIdentity(prisma, {
        connectionId: second.id,
        openId: "ou_collision",
        displayName: "Teacher B",
      }),
    ]);

    expect(identityA.userId).not.toBe(identityB.userId);
    expect(identityA.principalId).not.toBe(identityB.principalId);
    expect(scopedFeishuPrincipalId("CHAT", first.id, "oc_collision"))
      .not.toBe(scopedFeishuPrincipalId("CHAT", second.id, "oc_collision"));
    await expect(resolveScopedFeishuIdentity(prisma, {
      connectionId: first.id,
      openId: "ou_collision",
    })).resolves.toMatchObject({ organizationId: DEFAULT_ORG_ID, userId: identityA.userId });
    await expect(resolveScopedFeishuIdentity(prisma, {
      connectionId: second.id,
      openId: "ou_collision",
    })).resolves.toMatchObject({ organizationId: "org_identity_other", userId: identityB.userId });
  });

  it("fails closed for an Organization mismatch or disabled connection", async () => {
    const connection = await seedConnection(DEFAULT_ORG_ID, "identity-owner", "cli_identity");
    await upsertScopedFeishuIdentity(prisma, {
      connectionId: connection.id,
      openId: "ou_teacher",
      displayName: "Teacher",
    });

    await expect(resolveScopedFeishuIdentity(prisma, {
      connectionId: connection.id,
      expectedOrganizationId: "org_wrong",
      openId: "ou_teacher",
    })).rejects.toThrow("scope mismatch");
    await new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {}).disable({
      organizationId: DEFAULT_ORG_ID,
      actorUserId: "identity-owner",
    });
    await expect(resolveScopedFeishuIdentity(prisma, {
      connectionId: connection.id,
      openId: "ou_teacher",
    })).rejects.toThrow("active Feishu Application Connection not found");
  });

  it("serializes identity creation behind a concurrent connection disable", async () => {
    const connection = await seedConnection(DEFAULT_ORG_ID, "identity-owner", "cli_race");
    let release!: () => void;
    const releasePromise = new Promise<void>((resolve) => { release = resolve; });
    let locked!: () => void;
    const lockedPromise = new Promise<void>((resolve) => { locked = resolve; });
    const disabler = prisma.$transaction(async (tx) => {
      await tx.$queryRaw`
        SELECT "id" FROM "OrganizationFeishuApplicationConnection"
        WHERE "id" = ${connection.id} FOR UPDATE
      `;
      locked();
      await releasePromise;
      await tx.organizationFeishuApplicationConnection.update({
        where: { id: connection.id },
        data: { status: "DISABLED", disabledAt: new Date() },
      });
    }, { timeout: 10_000 });
    await lockedPromise;

    const identityAttempt = upsertScopedFeishuIdentity(prisma, {
      connectionId: connection.id,
      openId: "ou_racing_user",
      displayName: "Racing User",
    });
    const stateBeforeRelease = await Promise.race([
      identityAttempt.then(() => "settled", () => "settled"),
      new Promise<"blocked">((resolve) => setTimeout(() => resolve("blocked"), 50)),
    ]);
    expect(stateBeforeRelease).toBe("blocked");
    release();
    await disabler;
    await expect(identityAttempt).rejects.toThrow("active Feishu Application Connection not found");
    await expect(prisma.feishuUserIdentity.count({
      where: { connectionId: connection.id, openId: "ou_racing_user" },
    })).resolves.toBe(0);
  });
});

async function seedConnection(
  organizationId: string,
  actorUserId: string,
  appId: string,
): Promise<{ readonly id: string }> {
  await prisma.user.create({
    data: { id: actorUserId, feishuOpenId: `legacy_${actorUserId}`, displayName: actorUserId },
  });
  await prisma.organizationMembership.create({
    data: { organizationId, userId: actorUserId, role: "OWNER" },
  });
  return new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {})
    .rotateCustomerApplication({
      organizationId,
      actorUserId,
      appId,
      appSecret: `secret_${appId}`,
      botOpenId: `ou_bot_${appId}`,
    });
}
