import type { PermissionRole, Prisma, PrismaClient } from "@prisma/client";
import { lockActiveOrganization } from "../org/status.js";

export interface GrantTeamProjectAccessInput {
  readonly projectId: string;
  readonly teamId?: string | undefined;
  readonly teamSlug?: string | undefined;
  readonly role: PermissionRole;
  readonly createdByUserId?: string | undefined;
}

export interface RevokeTeamProjectAccessInput {
  readonly projectId: string;
  readonly teamId?: string | undefined;
  readonly teamSlug?: string | undefined;
}

export interface ProjectTeamAccessEntry {
  readonly grantId: string;
  readonly projectId: string;
  readonly organizationId: string;
  readonly teamId: string;
  readonly teamSlug: string;
  readonly teamName: string;
  readonly role: PermissionRole;
}

export async function grantTeamProjectAccess(
  prisma: PrismaClient,
  input: GrantTeamProjectAccessInput,
): Promise<ProjectTeamAccessEntry> {
  return prisma.$transaction(async (tx) => {
    const { project, team } = await resolveProjectAndTeam(tx, input);
    await lockActiveOrganization(tx, project.organizationId);
    const now = new Date();
    const existing = await tx.permissionGrant.findFirst({
      where: {
        resourceType: "PROJECT",
        resourceId: project.id,
        principalType: "TEAM",
        principalId: team.id,
        revokedAt: null,
      },
      select: { id: true, role: true },
    });
    if (existing?.role === input.role) {
      return entryFromGrant({
        grantId: existing.id,
        projectId: project.id,
        organizationId: project.organizationId,
        team,
        role: existing.role,
      });
    }
    await tx.permissionGrant.updateMany({
      where: {
        resourceType: "PROJECT",
        resourceId: project.id,
        principalType: "TEAM",
        principalId: team.id,
        revokedAt: null,
      },
      data: { revokedAt: now },
    });
    const grant = await tx.permissionGrant.create({
      data: {
        resourceType: "PROJECT",
        resourceId: project.id,
        principalType: "TEAM",
        principalId: team.id,
        role: input.role,
        ...(input.createdByUserId !== undefined ? { createdByUserId: input.createdByUserId } : {}),
      },
      select: { id: true, role: true },
    });
    return entryFromGrant({
      grantId: grant.id,
      projectId: project.id,
      organizationId: project.organizationId,
      team,
      role: grant.role,
    });
  });
}

export async function revokeTeamProjectAccess(
  prisma: PrismaClient,
  input: RevokeTeamProjectAccessInput,
): Promise<number> {
  return prisma.$transaction(async (tx) => {
    const { project, team } = await resolveProjectAndTeam(tx, input);
    await lockActiveOrganization(tx, project.organizationId);
    const result = await tx.permissionGrant.updateMany({
      where: {
        resourceType: "PROJECT",
        resourceId: project.id,
        principalType: "TEAM",
        principalId: team.id,
        revokedAt: null,
      },
      data: { revokedAt: new Date() },
    });
    return result.count;
  });
}

export async function listProjectTeamAccess(
  prisma: PrismaClient,
  projectId: string,
): Promise<readonly ProjectTeamAccessEntry[]> {
  const project = await prisma.project.findUnique({
    where: { id: projectId },
    select: { id: true, organizationId: true },
  });
  if (project === null) {
    throw new Error(`project not found: ${projectId}`);
  }
  const grants = await prisma.permissionGrant.findMany({
    where: {
      resourceType: "PROJECT",
      resourceId: projectId,
      principalType: "TEAM",
      revokedAt: null,
    },
    select: { id: true, principalId: true, role: true },
    orderBy: { createdAt: "asc" },
  });
  if (grants.length === 0) return [];

  const teams = await prisma.team.findMany({
    where: {
      organizationId: project.organizationId,
      id: { in: grants.map((grant) => grant.principalId) },
    },
    select: { id: true, slug: true, name: true, archivedAt: true },
  });
  const teamsById = new Map(teams.map((team) => [team.id, team]));
  const missing = grants.filter((grant) => !teamsById.has(grant.principalId));
  if (missing.length > 0) {
    throw new Error(
      `project ${projectId} has active TEAM grants outside organization ${project.organizationId}: ${missing.map((grant) => grant.principalId).join(", ")}`,
    );
  }

  return grants
    .filter((grant) => teamsById.get(grant.principalId)?.archivedAt === null)
    .map((grant) => entryFromGrant({
      grantId: grant.id,
      projectId: project.id,
      organizationId: project.organizationId,
      team: teamsById.get(grant.principalId)!,
      role: grant.role,
    }));
}

type ProjectForAccess = {
  readonly id: string;
  readonly organizationId: string;
};

type TeamForAccess = {
  readonly id: string;
  readonly slug: string;
  readonly name: string;
};

async function resolveProjectAndTeam(
  prisma: PrismaClient | Prisma.TransactionClient,
  input: { readonly projectId: string; readonly teamId?: string | undefined; readonly teamSlug?: string | undefined },
): Promise<{ readonly project: ProjectForAccess; readonly team: TeamForAccess }> {
  if ((input.teamId === undefined || input.teamId === "") && (input.teamSlug === undefined || input.teamSlug === "")) {
    throw new Error("granting team project access requires teamId or teamSlug");
  }
  if (input.teamId !== undefined && input.teamId !== "" && input.teamSlug !== undefined && input.teamSlug !== "") {
    throw new Error("granting team project access accepts only one of teamId or teamSlug");
  }

  const project = await prisma.project.findUnique({
    where: { id: input.projectId },
    select: { id: true, organizationId: true },
  });
  if (project === null) {
    throw new Error(`project not found: ${input.projectId}`);
  }

  const team = await prisma.team.findFirst({
    where:
      input.teamId !== undefined && input.teamId !== ""
        ? { id: input.teamId, archivedAt: null }
        : { organizationId: project.organizationId, slug: input.teamSlug!, archivedAt: null },
    select: { id: true, slug: true, name: true, organizationId: true },
  });
  if (team === null) {
    const label = input.teamId ?? input.teamSlug;
    throw new Error(`active team not found for project ${input.projectId}: ${label}`);
  }
  if (team.organizationId !== project.organizationId) {
    throw new Error(
      `cross-organization team grant refused: project ${project.id} is in ${project.organizationId}, team ${team.id} is in ${team.organizationId}`,
    );
  }

  return { project, team };
}

function entryFromGrant(input: {
  readonly grantId: string;
  readonly projectId: string;
  readonly organizationId: string;
  readonly team: TeamForAccess;
  readonly role: PermissionRole;
}): ProjectTeamAccessEntry {
  return {
    grantId: input.grantId,
    projectId: input.projectId,
    organizationId: input.organizationId,
    teamId: input.team.id,
    teamSlug: input.team.slug,
    teamName: input.team.name,
    role: input.role,
  };
}
