import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { archiveFeishuBindingForLifecycleEvent } from "../../src/feishu/bindingLifecycle.js";
import { scopedFeishuPrincipalId } from "../../src/feishu/identityNamespace.js";
import { DEFAULT_ORG_ID, prisma, resetDb, seedProject } from "./helpers.js";

const CONNECTION_ID = "feishu-connection-lifecycle";

describe("Feishu binding lifecycle events", () => {
  beforeEach(async () => {
    await resetDb();
  });

  afterAll(async () => prisma.$disconnect());

  it("archives a bound project and revokes its chat grant when the chat is dissolved", async () => {
    await seedProject("project-dissolved", "chat-dissolved");
    await seedFeishuConnection();
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "project-dissolved",
        principalType: "FEISHU_CHAT",
        principalId: scopedFeishuPrincipalId("CHAT", CONNECTION_ID, "chat-dissolved"),
        role: "EDIT",
      },
    });
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "project-dissolved",
        principalType: "FEISHU_CHAT",
        principalId: "chat-dissolved",
        role: "EDIT",
      },
    });
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "project-dissolved",
        principalType: "FEISHU_CHAT",
        principalId: scopedFeishuPrincipalId("CHAT", CONNECTION_ID, "chat-unrelated"),
        role: "EDIT",
      },
    });

    await expect(archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-dissolved",
      eventId: "event-dissolved",
      reason: "chat_dissolved",
    })).resolves.toEqual({ archived: true, projectId: "project-dissolved" });

    await expect(prisma.projectGroupBinding.findFirst({
      where: { chatId: "chat-dissolved" },
      select: { archivedAt: true },
    })).resolves.toMatchObject({ archivedAt: expect.any(Date) });
    await expect(prisma.permissionGrant.findFirst({
      where: {
        resourceId: "project-dissolved",
        principalId: scopedFeishuPrincipalId("CHAT", CONNECTION_ID, "chat-dissolved"),
      },
      select: { revokedAt: true },
    })).resolves.toMatchObject({ revokedAt: expect.any(Date) });
    await expect(prisma.permissionGrant.findFirst({
      where: { resourceId: "project-dissolved", principalId: "chat-dissolved" },
      select: { revokedAt: true },
    })).resolves.toMatchObject({ revokedAt: expect.any(Date) });
    await expect(prisma.permissionGrant.findFirst({
      where: {
        resourceId: "project-dissolved",
        principalId: scopedFeishuPrincipalId("CHAT", CONNECTION_ID, "chat-unrelated"),
      },
      select: { revokedAt: true },
    })).resolves.toEqual({ revokedAt: null });
    await expect(prisma.auditEntry.findFirst({
      where: { projectId: "project-dissolved", action: "project.chat_binding_archived" },
      select: { organizationId: true, metadata: true },
    })).resolves.toEqual({
      organizationId: DEFAULT_ORG_ID,
      metadata: { chatId: "chat-dissolved", eventId: "event-dissolved", reason: "chat_dissolved" },
    });
  });

  it("is idempotent when Feishu redelivers a lifecycle event", async () => {
    await seedProject("project-redelivery", "chat-redelivery");
    await seedFeishuConnection();

    await archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-redelivery",
      eventId: "event-redelivery",
      reason: "bot_removed",
    });
    await expect(archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-redelivery",
      eventId: "event-redelivery",
      reason: "bot_removed",
    })).resolves.toEqual({ archived: false });

    await expect(prisma.auditEntry.count({
      where: { projectId: "project-redelivery", action: "project.chat_binding_archived" },
    })).resolves.toBe(1);
  });

  it("does not archive a later binding when an old lifecycle event is redelivered", async () => {
    await seedProject("project-original", "chat-rebound");
    await prisma.project.create({
      data: {
        id: "project-rebound",
        organizationId: DEFAULT_ORG_ID,
        name: "Rebound project",
        workspaceDir: "/tmp/test-project-rebound",
      },
    });
    await seedFeishuConnection();

    await archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-rebound",
      eventId: "event-before-rebind",
      reason: "bot_removed",
    });
    await prisma.projectGroupBinding.create({
      data: {
        organizationId: DEFAULT_ORG_ID,
        projectId: "project-rebound",
        chatId: "chat-rebound",
        selectedAgentRoleId: `agent_role_draft_${DEFAULT_ORG_ID}`,
      },
    });

    await expect(archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-rebound",
      eventId: "event-before-rebind",
      reason: "bot_removed",
    })).resolves.toEqual({ archived: false });
    await expect(prisma.projectGroupBinding.findFirst({
      where: { projectId: "project-rebound", archivedAt: null },
      select: { id: true },
    })).resolves.not.toBeNull();
  });

  it("leaves unrelated active bindings untouched", async () => {
    await seedProject("project-kept", "chat-kept");

    await expect(archiveFeishuBindingForLifecycleEvent(prisma, {
      chatId: "chat-unknown",
      eventId: "event-unknown",
      reason: "chat_dissolved",
    })).resolves.toEqual({ archived: false });

    await expect(prisma.projectGroupBinding.findFirst({
      where: { chatId: "chat-kept", archivedAt: null },
      select: { id: true },
    })).resolves.not.toBeNull();
  });
});

async function seedFeishuConnection(): Promise<void> {
  await prisma.organizationFeishuApplicationConnection.create({
    data: {
      id: CONNECTION_ID,
      organizationId: DEFAULT_ORG_ID,
      appIdentityFingerprint: "fingerprint-lifecycle",
    },
  });
  await prisma.feishuApplicationCredentialVersion.create({
    data: {
      id: "feishu-secret-version-lifecycle",
      connectionId: CONNECTION_ID,
      version: 1,
      keyId: "test-active",
      envelope: { fixture: true },
    },
  });
  await prisma.organizationFeishuApplicationConnection.update({
    where: { id: CONNECTION_ID },
    data: {
      status: "ACTIVE",
      activeSecretVersionId: "feishu-secret-version-lifecycle",
      activatedAt: new Date(),
    },
  });
}
