import { Injectable } from '@nestjs/common';
import { Prisma } from '@/generated/prisma/client';
import { PrismaService } from '@/prisma/prisma.service';

@Injectable()
export class RolesRepository {
  constructor(private readonly prisma: PrismaService) {}

  private readonly publicSelect = {
    id: true,
    name: true,
    guardName: true,
    createdAt: true,
    updatedAt: true,
  } satisfies Prisma.RolesSelect;

  // Find many roles (optionally filtered by name).
  findMany(filter: { search?: string }) {
    const where: Prisma.RolesWhereInput = { deletedAt: null };
    if (filter.search) {
      where.name = { contains: filter.search, mode: 'insensitive' };
    }

    return this.prisma.roles.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      select: {
        ...this.publicSelect,
        permissions: {
          select: {
            permission: {
              select: {
                name: true,
                deletedAt: true,
              },
            },
          },
        },
      },
    });
  }

  // Find a single role by ID.
  findById(id: number) {
    return this.prisma.roles.findFirst({
      where: { id, deletedAt: null },
      select: this.publicSelect,
    });
  }

  // Find a role by its name within a guard (used for duplicate checks).
  findByName(name: string, guardName: string) {
    return this.prisma.roles.findFirst({
      where: { name, guardName, deletedAt: null },
      select: this.publicSelect,
    });
  }

  // Resolve a set of role IDs (used to validate assignments).
  findManyByIds(ids: number[]) {
    return this.prisma.roles.findMany({
      where: { id: { in: ids }, deletedAt: null },
      select: this.publicSelect,
    });
  }

  // Same as findById, but pulls in the permissions assigned to the role.
  findByIdWithPermissions(id: number) {
    return this.prisma.roles.findFirst({
      where: { id, deletedAt: null },
      select: {
        ...this.publicSelect,
        permissions: {
          select: {
            permission: {
              select: { id: true, name: true, guardName: true },
            },
          },
        },
      },
    });
  }

  // Insert a new role row.
  create(data: Prisma.RolesCreateInput) {
    return this.prisma.roles.create({
      data,
      select: this.publicSelect,
    });
  }

  // Update an existing role row by ID.
  update(id: number, data: Prisma.RolesUpdateInput) {
    return this.prisma.roles.update({
      where: { id },
      data,
      select: this.publicSelect,
    });
  }

  // "Soft delete" a role.
  softDelete(id: number) {
    return this.prisma.roles.update({
      where: { id },
      data: { deletedAt: new Date() },
      select: { id: true },
    });
  }

  // --- role_has_permissions pivot ---

  // Look up a single role/permission assignment.
  findRolePermission(roleId: number, permissionId: number) {
    return this.prisma.roleHasPermissions.findUnique({
      where: { roleId_permissionId: { roleId, permissionId } },
    });
  }

  // Assign permissions to a role (skips ones already assigned).
  addPermissions(roleId: number, permissionIds: number[]) {
    return this.prisma.roleHasPermissions.createMany({
      data: permissionIds.map((permissionId) => ({ roleId, permissionId })),
      skipDuplicates: true,
    });
  }

  // Replace the role's permissions with exactly `permissionIds` (empty clears all).
  setPermissions(roleId: number, permissionIds: number[]) {
    return this.prisma.$transaction(async (tx) => {
      await tx.roleHasPermissions.deleteMany({ where: { roleId } });

      if (permissionIds.length > 0) {
        await tx.roleHasPermissions.createMany({
          data: permissionIds.map((permissionId) => ({ roleId, permissionId })),
          skipDuplicates: true,
        });
      }
    });
  }

  // Remove a single permission from a role.
  removePermission(roleId: number, permissionId: number) {
    return this.prisma.roleHasPermissions.delete({
      where: { roleId_permissionId: { roleId, permissionId } },
      select: { roleId: true, permissionId: true },
    });
  }
}
