import { countPlanningEntries } from "@capakraken/application";
import type { Prisma, PrismaClient } from "@capakraken/db";
import { CreateRoleSchema, UpdateRoleSchema } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";

type RolePlanningCountsDb = Pick<PrismaClient, "demandRequirement" | "assignment">;
type RoleIdentifierDb = Pick<PrismaClient, "role">;

type RoleCountRecord = {
  id: string;
  _count: { resourceRoles: number };
};

type RoleListInput = {
  isActive?: boolean | undefined;
  search?: string | undefined;
};

type CreateRoleInput = z.infer<typeof CreateRoleSchema>;
type UpdateRoleInput = z.infer<typeof UpdateRoleSchema>;

export function buildRoleListWhere(input: RoleListInput): Prisma.RoleWhereInput {
  return {
    ...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
    ...(input.search
      ? { name: { contains: input.search, mode: "insensitive" } }
      : {}),
  };
}

export async function loadRolePlanningEntryCounts(
  db: RolePlanningCountsDb,
  roleIds: string[],
) {
  const { countsByRoleId } = await countPlanningEntries(db, {
    roleIds,
  });

  return countsByRoleId;
}

export async function attachRolePlanningEntryCounts<
  TRole extends RoleCountRecord,
>(
  db: RolePlanningCountsDb,
  roles: TRole[],
): Promise<Array<TRole & { _count: { resourceRoles: number; allocations: number } }>> {
  const countsByRoleId = await loadRolePlanningEntryCounts(
    db,
    roles.map((role) => role.id),
  );

  return roles.map((role) => ({
    ...role,
    _count: {
      ...role._count,
      allocations: countsByRoleId.get(role.id) ?? 0,
    },
  }));
}

export async function attachSingleRolePlanningEntryCount<
  TRole extends RoleCountRecord,
>(
  db: RolePlanningCountsDb,
  role: TRole,
): Promise<TRole & { _count: { resourceRoles: number; allocations: number } }> {
  return (await attachRolePlanningEntryCounts(db, [role]))[0]!;
}

export async function findRoleByIdentifier<TRole>(
  db: RoleIdentifierDb,
  identifier: string,
  select: Record<string, unknown>,
): Promise<TRole> {
  const normalizedIdentifier = identifier.trim();

  let role = await db.role.findUnique({
    where: { id: normalizedIdentifier },
    select,
  }) as TRole | null;
  if (!role) {
    role = await db.role.findUnique({
      where: { name: normalizedIdentifier },
      select,
    }) as TRole | null;
  }
  if (!role) {
    role = await db.role.findFirst({
      where: { name: { equals: normalizedIdentifier, mode: "insensitive" } },
      select,
    }) as TRole | null;
  }
  if (!role) {
    role = await db.role.findFirst({
      where: { name: { contains: normalizedIdentifier, mode: "insensitive" } },
      select,
    }) as TRole | null;
  }
  if (!role) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Role not found" });
  }

  return role;
}

export function buildRoleCreateData(
  input: CreateRoleInput,
): Prisma.RoleCreateInput {
  return {
    name: input.name,
    description: input.description ?? null,
    color: input.color ?? null,
  };
}

export function buildRoleUpdateData(
  input: UpdateRoleInput,
): Prisma.RoleUpdateInput {
  return {
    ...(input.name !== undefined ? { name: input.name } : {}),
    ...(input.description !== undefined ? { description: input.description } : {}),
    ...(input.color !== undefined ? { color: input.color } : {}),
    ...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
  };
}

export function appendZeroAllocationCount<
  TRole extends RoleCountRecord,
>(
  role: TRole,
): TRole & { _count: { resourceRoles: number; allocations: number } } {
  return {
    ...role,
    _count: {
      ...role._count,
      allocations: 0,
    },
  };
}

export async function assertRoleNameAvailable(
  db: RoleIdentifierDb,
  name: string,
  ignoreId?: string,
): Promise<void> {
  const existing = await db.role.findUnique({ where: { name } });
  if (existing && existing.id !== ignoreId) {
    throw new TRPCError({ code: "CONFLICT", message: `Role "${name}" already exists` });
  }
}
