import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization } from "./helpers.js";
import {
  createPermissionAuthorizer,
  grantTeamProjectAccess,
  listProjectTeamAccess,
  PrismaPrincipalResolver,
  syncExternalPrincipalMemberships,
} from "../../src/permission.js";
import { lockActiveOrganization } from "../../src/org/status.js";

describe("ADR-0019 authorization", () => {
  beforeEach(async () => {
    await resetDb();
  });

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

  it("resolves actor principals from user, chat, direct teams, external memberships, and external team bindings", async () => {
    const user = await prisma.user.create({
      data: { id: "u-auth-1", feishuOpenId: "ou_auth_1", displayName: "Teacher A" },
    });
    const directTeam = await prisma.team.create({
      data: { organizationId: DEFAULT_ORG_ID, slug: "direct-auth", name: "Direct Team" },
    });
    const departmentTeam = await prisma.team.create({
      data: { organizationId: DEFAULT_ORG_ID, slug: "department-auth", name: "Department Team" },
    });
    const chatTeam = await prisma.team.create({
      data: { organizationId: DEFAULT_ORG_ID, slug: "chat-auth", name: "Chat Team" },
    });
    await prisma.teamMembership.create({
      data: { teamId: directTeam.id, userId: user.id },
    });
    const connection = await prisma.externalDirectoryConnection.create({
      data: {
        organizationId: DEFAULT_ORG_ID,
        provider: "FEISHU",
        source: "test",
      },
    });
    await prisma.externalPrincipalMembership.create({
      data: {
        userId: user.id,
        connectionId: connection.id,
        principalType: "FEISHU_DEPARTMENT",
        principalId: "dep-physics",
      },
    });
    await prisma.teamExternalBinding.create({
      data: { teamId: departmentTeam.id, principalType: "FEISHU_DEPARTMENT", principalId: "dep-physics" },
    });
    await prisma.teamExternalBinding.create({
      data: { teamId: chatTeam.id, principalType: "FEISHU_CHAT", principalId: "chat-auth" },
    });

    const resolution = await new PrismaPrincipalResolver(prisma).resolveActor({
      feishuOpenId: "ou_auth_1",
      chatId: "chat-auth",
    }, { organizationId: DEFAULT_ORG_ID });

    expect(resolution.userId).toBe(user.id);
    expect(resolution.principals).toEqual(expect.arrayContaining([
      { type: "USER", id: "ou_auth_1" },
      { type: "FEISHU_CHAT", id: "chat-auth" },
      { type: "FEISHU_DEPARTMENT", id: "dep-physics" },
      { type: "TEAM", id: directTeam.id },
      { type: "TEAM", id: departmentTeam.id },
      { type: "TEAM", id: chatTeam.id },
    ]));
  });

  it("allows agent trigger through a team project grant", async () => {
    const { team } = await seedUserTeamProject("auth-team-grant");
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "p-auth-team-grant",
        principalType: "TEAM",
        principalId: team.id,
        role: "EDIT",
      },
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_team_grant" },
      action: "agent.trigger",
      resource: { type: "PROJECT", id: "p-auth-team-grant" },
    });

    expect(decision.allowed).toBe(true);
    expect(decision.matchedGrant).toMatchObject({
      principal: { type: "TEAM", id: team.id },
      role: "EDIT",
    });
  });

  it.each(["SUSPENDED", "ARCHIVED"] as const)(
    "denies every project authorization when the organization is %s",
    async (status) => {
      await prisma.project.create({
        data: { id: `p-org-${status}`, organizationId: DEFAULT_ORG_ID, name: status, workspaceDir: `/tmp/org-${status}` },
      });
      await prisma.user.create({
        data: {
          id: `u-org-${status}`,
          feishuOpenId: `ou_org_${status}`,
          displayName: status,
          organizationMemberships: { create: { organizationId: DEFAULT_ORG_ID, role: "OWNER" } },
        },
      });
      await prisma.permissionGrant.create({
        data: {
          resourceType: "PROJECT",
          resourceId: `p-org-${status}`,
          principalType: "USER",
          principalId: `ou_org_${status}`,
          role: "MANAGE",
        },
      });
      await prisma.organization.update({ where: { id: DEFAULT_ORG_ID }, data: { status } });

      for (const action of ["project.read", "project.edit", "collaborator.manage", "agent.trigger", "agent.cancel"] as const) {
        const decision = await createPermissionAuthorizer(prisma).can({
          actor: { feishuOpenId: `ou_org_${status}` },
          action,
          resource: { type: "PROJECT", id: `p-org-${status}` },
        });
        expect(decision.allowed).toBe(false);
        expect(decision.reason).toContain(`organization ${DEFAULT_ORG_ID} is ${status}`);
      }
    },
  );

  it("serializes a lifecycle transition behind an admitted customer transaction", async () => {
    let releaseOperation!: () => void;
    const operationHeld = new Promise<void>((resolve) => {
      releaseOperation = resolve;
    });
    let markLocked!: () => void;
    const rowLocked = new Promise<void>((resolve) => {
      markLocked = resolve;
    });
    const operation = prisma.$transaction(async (tx) => {
      await lockActiveOrganization(tx, DEFAULT_ORG_ID);
      markLocked();
      await operationHeld;
    });
    await rowLocked;

    let transitionCommitted = false;
    const transition = prisma.organization.update({
      where: { id: DEFAULT_ORG_ID },
      data: { status: "SUSPENDED" },
    }).then(() => {
      transitionCommitted = true;
    });
    await new Promise((resolve) => setTimeout(resolve, 100));
    expect(transitionCommitted).toBe(false);

    releaseOperation();
    await operation;
    await transition;
    expect(transitionCommitted).toBe(true);
  });

  it("uses external Feishu principal memberships as grantable principals", async () => {
    await prisma.project.create({
      data: { id: "p-auth-ext", organizationId: DEFAULT_ORG_ID, name: "External", workspaceDir: "/tmp/auth-ext" },
    });
    await syncExternalPrincipalMemberships(prisma, {
      organizationId: DEFAULT_ORG_ID,
      source: "feishu-test",
      memberships: [
        {
          feishuOpenId: "ou_auth_ext",
          displayName: "Teacher Ext",
          principalType: "FEISHU_USER_GROUP",
          principalId: "ug-lesson-design",
        },
      ],
    });
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "p-auth-ext",
        principalType: "FEISHU_USER_GROUP",
        principalId: "ug-lesson-design",
        role: "EDIT",
      },
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_ext" },
      action: "agent.trigger",
      resource: { type: "PROJECT", id: "p-auth-ext" },
    });

    expect(decision.allowed).toBe(true);
    expect(decision.matchedGrant?.principal).toEqual({
      type: "FEISHU_USER_GROUP",
      id: "ug-lesson-design",
    });
  });

  it("revokes missing external memberships when replacing a sync source", async () => {
    await syncExternalPrincipalMemberships(prisma, {
      organizationId: DEFAULT_ORG_ID,
      source: "feishu-directory",
      memberships: [
        {
          feishuOpenId: "ou_sync_a",
          displayName: "Teacher A",
          principalType: "FEISHU_DEPARTMENT",
          principalId: "dep-a",
        },
        {
          feishuOpenId: "ou_sync_b",
          displayName: "Teacher B",
          principalType: "FEISHU_DEPARTMENT",
          principalId: "dep-b",
        },
      ],
    });

    const result = await syncExternalPrincipalMemberships(prisma, {
      organizationId: DEFAULT_ORG_ID,
      source: "feishu-directory",
      replaceSource: true,
      memberships: [
        {
          feishuOpenId: "ou_sync_a",
          displayName: "Teacher A",
          principalType: "FEISHU_DEPARTMENT",
          principalId: "dep-a",
        },
      ],
    });

    expect(result).toEqual({ synced: 1, revoked: 1 });
    const activeB = await new PrismaPrincipalResolver(prisma).resolveActor(
      { feishuOpenId: "ou_sync_b" },
      { organizationId: DEFAULT_ORG_ID },
    );
    expect(activeB.principals).not.toContainEqual({ type: "FEISHU_DEPARTMENT", id: "dep-b" });
  });

  it("blocks directory sync and team grants for an archived organization", async () => {
    const team = await prisma.team.create({
      data: { organizationId: DEFAULT_ORG_ID, slug: "archived-team", name: "Archived Team" },
    });
    await prisma.project.create({
      data: { id: "p-archived-grant", organizationId: DEFAULT_ORG_ID, name: "Archived", workspaceDir: "/tmp/archived" },
    });
    await prisma.organization.update({ where: { id: DEFAULT_ORG_ID }, data: { status: "ARCHIVED" } });
    const expected = `organization ${DEFAULT_ORG_ID} is ARCHIVED`;

    await expect(syncExternalPrincipalMemberships(prisma, {
      organizationId: DEFAULT_ORG_ID,
      source: "blocked-sync",
      memberships: [{
        feishuOpenId: "ou_blocked_sync",
        displayName: "Blocked",
        principalType: "FEISHU_DEPARTMENT",
        principalId: "dep-blocked",
      }],
    })).rejects.toThrow(expected);
    await expect(grantTeamProjectAccess(prisma, {
      projectId: "p-archived-grant",
      teamId: team.id,
      role: "EDIT",
    })).rejects.toThrow(expected);

    await expect(prisma.externalDirectoryConnection.count()).resolves.toBe(0);
    await expect(prisma.permissionGrant.count({ where: { resourceId: "p-archived-grant" } })).resolves.toBe(0);
  });

  it("lets PermissionSettings constrain agent trigger without granting by itself", async () => {
    await prisma.project.create({
      data: { id: "p-auth-policy", organizationId: DEFAULT_ORG_ID, name: "Policy", workspaceDir: "/tmp/auth-policy" },
    });
    await prisma.user.create({
      data: { id: "u-auth-policy", feishuOpenId: "ou_auth_policy", displayName: "Teacher Policy" },
    });
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "p-auth-policy",
        principalType: "USER",
        principalId: "ou_auth_policy",
        role: "EDIT",
      },
    });
    await prisma.permissionSettings.create({
      data: defaultProjectSettings("p-auth-policy", { agentTrigger: "MANAGE_ONLY" }),
    });

    const editDecision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_policy" },
      action: "agent.trigger",
      resource: { type: "PROJECT", id: "p-auth-policy" },
    });
    expect(editDecision.allowed).toBe(false);

    await prisma.permissionGrant.updateMany({
      where: {
        resourceType: "PROJECT",
        resourceId: "p-auth-policy",
        principalType: "USER",
        principalId: "ou_auth_policy",
        revokedAt: null,
      },
      data: {
        role: "MANAGE",
      },
    });

    const manageDecision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_policy" },
      action: "agent.trigger",
      resource: { type: "PROJECT", id: "p-auth-policy" },
    });
    expect(manageDecision.allowed).toBe(true);
    expect(manageDecision.requiredRole).toBe("MANAGE");
  });

  it("requires project agent.trigger and configured role trigger grant for role.trigger", async () => {
    const { team } = await seedUserTeamProject("auth-role");
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "p-auth-role",
        principalType: "TEAM",
        principalId: team.id,
        role: "EDIT",
      },
    });
    await prisma.roleTriggerGrant.create({
      data: {
        projectId: "p-auth-role",
        roleId: "review",
        principalType: "USER",
        principalId: "ou_someone_else",
      },
    });

    const denied = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_role" },
      action: "role.trigger",
      resource: { type: "PROJECT", id: "p-auth-role" },
      roleId: "review",
    });
    expect(denied.allowed).toBe(false);

    await prisma.roleTriggerGrant.create({
      data: {
        projectId: "p-auth-role",
        roleId: "review",
        principalType: "TEAM",
        principalId: team.id,
      },
    });

    const allowed = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_role" },
      action: "role.trigger",
      resource: { type: "PROJECT", id: "p-auth-role" },
      roleId: "review",
    });
    expect(allowed.allowed).toBe(true);
    expect(allowed.matchedRoleGrant?.principal).toEqual({ type: "TEAM", id: team.id });
  });

  it("keeps a role configured when all role grants are revoked", async () => {
    await prisma.project.create({
      data: { id: "p-auth-revoked-role", organizationId: DEFAULT_ORG_ID, name: "Revoked", workspaceDir: "/tmp/auth-revoked" },
    });
    await prisma.user.create({
      data: { id: "u-auth-revoked-role", feishuOpenId: "ou_auth_revoked", displayName: "Teacher Revoked" },
    });
    await prisma.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: "p-auth-revoked-role",
        principalType: "USER",
        principalId: "ou_auth_revoked",
        role: "EDIT",
      },
    });
    await prisma.roleTriggerGrant.create({
      data: {
        projectId: "p-auth-revoked-role",
        roleId: "review",
        principalType: "USER",
        principalId: "ou_auth_revoked",
        revokedAt: new Date(),
      },
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_auth_revoked" },
      action: "role.trigger",
      resource: { type: "PROJECT", id: "p-auth-revoked-role" },
      roleId: "review",
    });

    expect(decision.allowed).toBe(false);
    expect(decision.reason).toContain("no active role review grant");
  });
  it("agent.cancel defaults to MANAGE and is allowed for a manage grant", async () => {
    await prisma.project.create({ data: { id: "p-cancel", organizationId: DEFAULT_ORG_ID, name: "Cancel", workspaceDir: "/tmp/cancel" } });
    await prisma.user.create({ data: { id: "u-cancel", feishuOpenId: "ou_cancel", displayName: "Manager" } });
    await prisma.permissionGrant.create({
      data: { resourceType: "PROJECT", resourceId: "p-cancel", principalType: "USER", principalId: "ou_cancel", role: "MANAGE" },
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_cancel" },
      action: "agent.cancel",
      resource: { type: "PROJECT", id: "p-cancel" },
    });
    expect(decision.allowed).toBe(true);
    expect(decision.requiredRole).toBe("MANAGE");
  });

  it("agent.cancel is denied to an edit grant (below the MANAGE default)", async () => {
    await prisma.project.create({ data: { id: "p-cancel-edit", organizationId: DEFAULT_ORG_ID, name: "Cancel Edit", workspaceDir: "/tmp/cancel-edit" } });
    await prisma.user.create({ data: { id: "u-cancel-edit", feishuOpenId: "ou_cancel_edit", displayName: "Editor" } });
    await prisma.permissionGrant.create({
      data: { resourceType: "PROJECT", resourceId: "p-cancel-edit", principalType: "USER", principalId: "ou_cancel_edit", role: "EDIT" },
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_cancel_edit" },
      action: "agent.cancel",
      resource: { type: "PROJECT", id: "p-cancel-edit" },
    });
    expect(decision.allowed).toBe(false);
  });

  it("agentCancel DISABLED policy denies cancel even for a manage grant", async () => {
    await prisma.project.create({ data: { id: "p-cancel-off", organizationId: DEFAULT_ORG_ID, name: "Cancel Off", workspaceDir: "/tmp/cancel-off" } });
    await prisma.user.create({ data: { id: "u-cancel-off", feishuOpenId: "ou_cancel_off", displayName: "Manager Off" } });
    await prisma.permissionGrant.create({
      data: { resourceType: "PROJECT", resourceId: "p-cancel-off", principalType: "USER", principalId: "ou_cancel_off", role: "MANAGE" },
    });
    await prisma.permissionSettings.create({
      data: defaultProjectSettings("p-cancel-off", { agentCancel: "DISABLED" }),
    });

    const decision = await createPermissionAuthorizer(prisma).can({
      actor: { feishuOpenId: "ou_cancel_off" },
      action: "agent.cancel",
      resource: { type: "PROJECT", id: "p-cancel-off" },
    });
    expect(decision.allowed).toBe(false);
    expect(decision.reason).toContain("disables this action");
  });

  it("scopes team slug and project team access by organization", async () => {
    await seedTestOrganization("org-other", "other");
    await prisma.team.create({
      data: { organizationId: "org-other", slug: "curriculum", name: "Other Curriculum" },
    });
    const team = await prisma.team.create({
      data: { organizationId: DEFAULT_ORG_ID, slug: "curriculum", name: "Default Curriculum" },
    });
    await prisma.project.create({
      data: { id: "p-team-access", organizationId: DEFAULT_ORG_ID, name: "Team Access", workspaceDir: "/tmp/team-access" },
    });

    const entry = await grantTeamProjectAccess(prisma, {
      projectId: "p-team-access",
      teamSlug: "curriculum",
      role: "EDIT",
    });

    expect(entry).toMatchObject({
      projectId: "p-team-access",
      organizationId: DEFAULT_ORG_ID,
      teamId: team.id,
      teamSlug: "curriculum",
      role: "EDIT",
    });
    await grantTeamProjectAccess(prisma, {
      projectId: "p-team-access",
      teamSlug: "curriculum",
      role: "MANAGE",
    });
    const entries = await listProjectTeamAccess(prisma, "p-team-access");
    expect(entries).toHaveLength(1);
    expect(entries[0]).toMatchObject({ teamId: team.id, role: "MANAGE" });
  });

  it("refuses cross-organization team project grants", async () => {
    await seedTestOrganization("org-other-deny", "other-deny");
    const otherTeam = await prisma.team.create({
      data: { organizationId: "org-other-deny", slug: "review", name: "Other Review" },
    });
    await prisma.project.create({
      data: { id: "p-cross-org", organizationId: DEFAULT_ORG_ID, name: "Cross Org", workspaceDir: "/tmp/cross-org" },
    });

    await expect(grantTeamProjectAccess(prisma, {
      projectId: "p-cross-org",
      teamId: otherTeam.id,
      role: "EDIT",
    })).rejects.toThrow(/cross-organization team grant refused/);
  });
});

async function seedUserTeamProject(suffix: string) {
  const user = await prisma.user.create({
    data: {
      id: `u-${suffix}`,
      feishuOpenId: `ou_${suffix.replaceAll("-", "_")}`,
      displayName: "Teacher Team",
    },
  });
  const team = await prisma.team.create({
    data: { organizationId: DEFAULT_ORG_ID, slug: `team-${suffix}`, name: `Team ${suffix}` },
  });
  await prisma.teamMembership.create({
    data: { teamId: team.id, userId: user.id },
  });
  const project = await prisma.project.create({
    data: { id: `p-${suffix}`, organizationId: DEFAULT_ORG_ID, name: `Project ${suffix}`, workspaceDir: `/tmp/${suffix}` },
  });
  return { user, team, project };
}

function defaultProjectSettings(
  projectId: string,
  overrides: { agentTrigger?: string; agentCancel?: string } = {},
) {
  return {
    resourceType: "PROJECT" as const,
    resourceId: projectId,
    externalShare: "ROLE",
    comment: "ROLE",
    copyDownload: "ROLE",
    collaboratorMgmt: "ROLE",
    agentTrigger: overrides.agentTrigger ?? "ROLE",
    agentCancel: overrides.agentCancel ?? "ROLE",
  };
}
