-- ADR-0019: typed principals, Hub teams, and external Feishu principal sync.

CREATE TYPE "PrincipalType" AS ENUM (
  'USER',
  'TEAM',
  'FEISHU_CHAT',
  'FEISHU_DEPARTMENT',
  'FEISHU_USER_GROUP',
  'APP'
);

CREATE TABLE "Team" (
  "id" TEXT NOT NULL,
  "slug" TEXT NOT NULL,
  "name" TEXT NOT NULL,
  "description" TEXT,
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "updatedAt" TIMESTAMP(3) NOT NULL,
  "archivedAt" TIMESTAMP(3),

  CONSTRAINT "Team_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "Team_slug_key" ON "Team"("slug");
CREATE INDEX "Team_archivedAt_idx" ON "Team"("archivedAt");

CREATE TABLE "TeamMembership" (
  "id" TEXT NOT NULL,
  "teamId" TEXT NOT NULL,
  "userId" TEXT NOT NULL,
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "revokedAt" TIMESTAMP(3),

  CONSTRAINT "TeamMembership_pkey" PRIMARY KEY ("id")
);

CREATE INDEX "TeamMembership_teamId_revokedAt_idx" ON "TeamMembership"("teamId", "revokedAt");
CREATE INDEX "TeamMembership_userId_revokedAt_idx" ON "TeamMembership"("userId", "revokedAt");
CREATE UNIQUE INDEX "TeamMembership_teamId_userId_revokedAt_key" ON "TeamMembership"("teamId", "userId", "revokedAt");
CREATE UNIQUE INDEX "TeamMembership_active_key" ON "TeamMembership"("teamId", "userId") WHERE "revokedAt" IS NULL;

ALTER TABLE "TeamMembership" ADD CONSTRAINT "TeamMembership_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TeamMembership" ADD CONSTRAINT "TeamMembership_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

CREATE TABLE "TeamExternalBinding" (
  "id" TEXT NOT NULL,
  "teamId" TEXT NOT NULL,
  "principalType" "PrincipalType" NOT NULL,
  "principalId" TEXT NOT NULL,
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "revokedAt" TIMESTAMP(3),

  CONSTRAINT "TeamExternalBinding_pkey" PRIMARY KEY ("id")
);

CREATE INDEX "TeamExternalBinding_principalType_principalId_revokedAt_idx" ON "TeamExternalBinding"("principalType", "principalId", "revokedAt");
CREATE INDEX "TeamExternalBinding_teamId_revokedAt_idx" ON "TeamExternalBinding"("teamId", "revokedAt");
CREATE UNIQUE INDEX "TeamExternalBinding_teamId_principalType_principalId_revokedAt_key" ON "TeamExternalBinding"("teamId", "principalType", "principalId", "revokedAt");
CREATE UNIQUE INDEX "TeamExternalBinding_active_key" ON "TeamExternalBinding"("teamId", "principalType", "principalId") WHERE "revokedAt" IS NULL;

ALTER TABLE "TeamExternalBinding" ADD CONSTRAINT "TeamExternalBinding_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;

CREATE TABLE "ExternalPrincipalMembership" (
  "id" TEXT NOT NULL,
  "userId" TEXT NOT NULL,
  "principalType" "PrincipalType" NOT NULL,
  "principalId" TEXT NOT NULL,
  "source" TEXT NOT NULL,
  "syncedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "revokedAt" TIMESTAMP(3),

  CONSTRAINT "ExternalPrincipalMembership_pkey" PRIMARY KEY ("id")
);

CREATE INDEX "ExternalPrincipalMembership_userId_revokedAt_idx" ON "ExternalPrincipalMembership"("userId", "revokedAt");
CREATE INDEX "ExternalPrincipalMembership_principalType_principalId_revokedAt_idx" ON "ExternalPrincipalMembership"("principalType", "principalId", "revokedAt");
CREATE INDEX "ExternalPrincipalMembership_source_syncedAt_idx" ON "ExternalPrincipalMembership"("source", "syncedAt");
CREATE UNIQUE INDEX "ExternalPrincipalMembership_userId_principalType_principalId_source_revokedAt_key" ON "ExternalPrincipalMembership"("userId", "principalType", "principalId", "source", "revokedAt");
CREATE UNIQUE INDEX "ExternalPrincipalMembership_active_key" ON "ExternalPrincipalMembership"("userId", "principalType", "principalId", "source") WHERE "revokedAt" IS NULL;

ALTER TABLE "ExternalPrincipalMembership" ADD CONSTRAINT "ExternalPrincipalMembership_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- PermissionGrant: backfill legacy opaque principal strings as USER principals.
ALTER TABLE "PermissionGrant" DROP CONSTRAINT IF EXISTS "PermissionGrant_resourceId_fkey";
DROP INDEX IF EXISTS "PermissionGrant_resourceType_resourceId_principal_role_revo_key";
DROP INDEX IF EXISTS "PermissionGrant_principal_revokedAt_idx";

ALTER TABLE "PermissionGrant" ADD COLUMN "principalType" "PrincipalType" NOT NULL DEFAULT 'USER';
ALTER TABLE "PermissionGrant" ADD COLUMN "principalId" TEXT;
UPDATE "PermissionGrant" SET "principalId" = "principal";
ALTER TABLE "PermissionGrant" ALTER COLUMN "principalId" SET NOT NULL;
ALTER TABLE "PermissionGrant" ALTER COLUMN "principalType" DROP DEFAULT;
ALTER TABLE "PermissionGrant" DROP COLUMN "principal";

WITH ranked_permission_grants AS (
  SELECT
    "id",
    row_number() OVER (
      PARTITION BY "resourceType", "resourceId", "principalType", "principalId", "role"
      ORDER BY "createdAt", "id"
    ) AS rn
  FROM "PermissionGrant"
  WHERE "revokedAt" IS NULL
)
DELETE FROM "PermissionGrant"
WHERE "id" IN (
  SELECT "id" FROM ranked_permission_grants WHERE rn > 1
);

CREATE INDEX "PermissionGrant_principalType_principalId_revokedAt_idx" ON "PermissionGrant"("principalType", "principalId", "revokedAt");
CREATE UNIQUE INDEX "PermissionGrant_active_key" ON "PermissionGrant"("resourceType", "resourceId", "principalType", "principalId", "role") WHERE "revokedAt" IS NULL;

-- PermissionSettings resource is polymorphic; do not FK resourceId to Project.
ALTER TABLE "PermissionSettings" DROP CONSTRAINT IF EXISTS "PermissionSettings_resourceId_fkey";

-- RoleTriggerGrant: same backfill as PermissionGrant.
DROP INDEX IF EXISTS "RoleTriggerGrant_projectId_roleId_principal_revokedAt_key";
DROP INDEX IF EXISTS "RoleTriggerGrant_principal_revokedAt_idx";

ALTER TABLE "RoleTriggerGrant" ADD COLUMN "principalType" "PrincipalType" NOT NULL DEFAULT 'USER';
ALTER TABLE "RoleTriggerGrant" ADD COLUMN "principalId" TEXT;
UPDATE "RoleTriggerGrant" SET "principalId" = "principal";
ALTER TABLE "RoleTriggerGrant" ALTER COLUMN "principalId" SET NOT NULL;
ALTER TABLE "RoleTriggerGrant" ALTER COLUMN "principalType" DROP DEFAULT;
ALTER TABLE "RoleTriggerGrant" DROP COLUMN "principal";

WITH ranked_role_trigger_grants AS (
  SELECT
    "id",
    row_number() OVER (
      PARTITION BY "projectId", "roleId", "principalType", "principalId"
      ORDER BY "createdAt", "id"
    ) AS rn
  FROM "RoleTriggerGrant"
  WHERE "revokedAt" IS NULL
)
DELETE FROM "RoleTriggerGrant"
WHERE "id" IN (
  SELECT "id" FROM ranked_role_trigger_grants WHERE rn > 1
);

CREATE INDEX "RoleTriggerGrant_principalType_principalId_revokedAt_idx" ON "RoleTriggerGrant"("principalType", "principalId", "revokedAt");
CREATE UNIQUE INDEX "RoleTriggerGrant_active_key" ON "RoleTriggerGrant"("projectId", "roleId", "principalType", "principalId") WHERE "revokedAt" IS NULL;
