import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ConflictException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';

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

  /** List all roles: built-in (clubId=null) + custom for a specific club */
  async findAll(clubId: string | null) {
    const roles = await this.prisma.role.findMany({
      where: {
        OR: [{ clubId: null }, ...(clubId ? [{ clubId }] : [])],
      },
      include: { _count: { select: { userRoles: true } } },
      orderBy: [{ isBuiltIn: 'desc' }, { name: 'asc' }],
    });

    return roles.map((r) => ({
      id: r.id,
      clubId: r.clubId,
      name: r.name,
      slug: r.slug,
      isBuiltIn: r.isBuiltIn,
      isActive: r.isActive,
      userCount: r._count.userRoles,
      createdAt: r.createdAt,
    }));
  }

  /** Create a custom role for a club */
  async create(clubId: string, data: { name: string; slug: string }) {
    if (!data.name?.trim() || !data.slug?.trim()) {
      throw new BadRequestException('name and slug are required');
    }

    const slug = data.slug.toLowerCase().replace(/[^a-z0-9_]/g, '_');

    // Check uniqueness
    const existing = await this.prisma.role.findFirst({
      where: { clubId, slug },
    });
    if (existing) {
      throw new ConflictException(`Role with slug "${slug}" already exists`);
    }

    return this.prisma.role.create({
      data: {
        clubId,
        name: data.name.trim(),
        slug,
        isBuiltIn: false,
        isActive: true,
      },
    });
  }

  /** Rename a role (built-in: only if allowed, custom: always) */
  async rename(id: string, name: string) {
    const role = await this.prisma.role.findUnique({ where: { id } });
    if (!role) throw new NotFoundException('Role not found');

    // Built-in roles that can't be renamed
    const nonRenameable = ['super_admin', 'club_admin', 'trainer'];
    if (role.isBuiltIn && nonRenameable.includes(role.slug)) {
      throw new BadRequestException(`Built-in role "${role.slug}" cannot be renamed`);
    }

    return this.prisma.role.update({
      where: { id },
      data: { name: name.trim() },
    });
  }

  /** Delete a custom role (not built-in, no users assigned) */
  async delete(id: string) {
    const role = await this.prisma.role.findUnique({
      where: { id },
      include: { _count: { select: { userRoles: true } } },
    });
    if (!role) throw new NotFoundException('Role not found');

    if (role.isBuiltIn) {
      throw new BadRequestException('Built-in roles cannot be deleted');
    }
    if (role._count.userRoles > 0) {
      throw new BadRequestException(
        `Role "${role.name}" has ${role._count.userRoles} users assigned. Remove assignments first.`,
      );
    }

    return this.prisma.role.delete({ where: { id } });
  }

  /** Assign roles to a user (replaces existing assignments) */
  async assignRoles(userId: string, roleIds: string[]) {
    if (!roleIds.length) {
      throw new BadRequestException('At least one role is required');
    }

    // Validate all roles exist
    const roles = await this.prisma.role.findMany({
      where: { id: { in: roleIds } },
    });
    if (roles.length !== roleIds.length) {
      throw new BadRequestException('One or more role IDs are invalid');
    }

    // Delete existing and create new in transaction
    await this.prisma.$transaction([
      this.prisma.userRoleMap.deleteMany({ where: { userId } }),
      ...roleIds.map((roleId) =>
        this.prisma.userRoleMap.create({
          data: { userId, roleId },
        }),
      ),
    ]);

    // Also update legacy User.role field for backward compat
    const roleSlugs = roles.map((r) => r.slug);
    const primaryRole = this.getPrimaryRole(roleSlugs);
    const enumValue = primaryRole.toUpperCase();

    await this.prisma.$executeRawUnsafe(
      `UPDATE users SET role = $1::"UserRole", "updatedAt" = NOW() WHERE id = $2`,
      enumValue,
      userId,
    );

    return { userId, roleIds };
  }

  /** Get roles for a user */
  async getUserRoles(userId: string) {
    const mappings = await this.prisma.userRoleMap.findMany({
      where: { userId },
      include: { role: true },
    });
    return mappings.map((m) => ({
      id: m.role.id,
      name: m.role.name,
      slug: m.role.slug,
      isBuiltIn: m.role.isBuiltIn,
    }));
  }

  private getPrimaryRole(slugs: string[]): string {
    const priority = [
      'super_admin',
      'club_admin',
      'manager',
      'coordinator',
      'receptionist',
      'trainer',
    ];
    for (const p of priority) {
      if (slugs.includes(p)) return p;
    }
    return slugs[0] || 'trainer';
  }
}
