import { Injectable, Logger, Optional } from '@nestjs/common';
import type { UpdateProfileDto } from './dto/rbac.dto';
import { RbacSeedService } from './rbac-seed.service';
import { RbacUserService } from './rbac-user.service';
import { getChinaDateParts } from './rbac-presets';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import {
  Permission,
  Role,
  User,
  Class,
  ClassStudent,
  ClassTeacher,
  ClassSchedule,
  Student,
  AttendanceSession,
} from '../entities';

@Injectable()
export class RbacService {
  private readonly logger = new Logger(RbacService.name);

  constructor(
    @InjectRepository(Permission) private permRepo: Repository<Permission>,
    @InjectRepository(Role) private roleRepo: Repository<Role>,
    @InjectRepository(User) private userRepo: Repository<User>,
    @InjectRepository(Class) private classRepo: Repository<Class>,
    @InjectRepository(ClassStudent) private classStudentRepo: Repository<ClassStudent>,
    @InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
    @InjectRepository(ClassSchedule) private classScheduleRepo: Repository<ClassSchedule>,
    @InjectRepository(Student) private studentRepo: Repository<Student>,
    @InjectRepository(AttendanceSession)
    private attendanceSessionRepo: Repository<AttendanceSession>,
    @Optional() private seedService?: RbacSeedService,
    @Optional() private userService?: RbacUserService,
  ) {}

  private get seedOps(): RbacSeedService {
    if (!this.seedService) {
      this.seedService = new RbacSeedService(this.permRepo, this.roleRepo, this.userRepo);
    }
    return this.seedService;
  }

  private get userOps(): RbacUserService {
    if (!this.userService) {
      this.userService = new RbacUserService(
        this.permRepo,
        this.roleRepo,
        this.userRepo,
        this.classRepo,
        this.classStudentRepo,
        this.classTeacherRepo,
        this.classScheduleRepo,
        this.studentRepo,
        this.attendanceSessionRepo,
      );
    }
    return this.userService;
  }

  async findAllRoles(): Promise<Role[]> {
    return this.roleRepo.find({
      relations: ['permissions'],
      order: { id: 'ASC' },
    });
  }

  async findRoleById(id: number): Promise<Role> {
    return this.roleRepo.findOneOrFail({ where: { id }, relations: ['permissions'] });
  }

  async createRole(dto: {
    name: string;
    description?: string;
    permissionIds?: number[];
  }): Promise<Role> {
    const role = this.roleRepo.create({ name: dto.name, description: dto.description });
    if (dto.permissionIds && dto.permissionIds.length > 0) {
      role.permissions = await this.userOps.resolvePermissions(dto.permissionIds);
    }
    return this.roleRepo.save(role);
  }

  async updateRole(
    id: number,
    dto: { name?: string; description?: string; permissionIds?: number[] },
  ): Promise<Role> {
    const role = await this.roleRepo.findOneOrFail({
      where: { id },
      relations: ['permissions'],
    });
    if (dto.name !== undefined && !role.isSystem) role.name = dto.name;
    if (dto.description !== undefined) role.description = dto.description;
    if (dto.permissionIds !== undefined) {
      role.permissions =
        dto.permissionIds.length > 0 ? await this.userOps.resolvePermissions(dto.permissionIds) : [];
    }
    return this.roleRepo.save(role);
  }

  async deleteRole(id: number): Promise<{ message: string }> {
    const role = await this.roleRepo.findOneOrFail({ where: { id } });
    if (role.isSystem) throw new Error('系统角色不可停用');
    if (role.status === 0) return { message: '角色已停用' };
    role.status = 0;
    await this.roleRepo.save(role);
    return { message: '角色已停用' };
  }

  async findAllPermissions(): Promise<Permission[]> {
    return this.permRepo.find({ order: { group: 'ASC', code: 'ASC' } });
  }

  async getPermissionTree(): Promise<{ group: string; permissions: Permission[] }[]> {
    const all = await this.findAllPermissions();
    const map = new Map<string, Permission[]>();
    for (const p of all) {
      if (!map.has(p.group)) map.set(p.group, []);
      map.get(p.group)!.push(p);
    }
    return Array.from(map.entries()).map(([group, permissions]) => ({ group, permissions }));
  }

  async getUserPermissions(userId: number): Promise<string[]> {
    const user = await this.userRepo.findOne({
      where: { id: userId },
      relations: ['roles', 'roles.permissions'],
    });
    if (!user || !user.roles) return [];
    const codes = new Set<string>();
    for (const role of user.roles) {
      if (role.status !== 1) continue;
      for (const perm of role.permissions) {
        codes.add(perm.code);
      }
    }
    return Array.from(codes);
  }


  async getTeacherWorkspace(userId: number) {
    // Find all classes where this user is a teacher
    const teacherAssignments = await this.classTeacherRepo.find({
      where: { userId },
      relations: ['class'],
    });

    const classIds = [...new Set(teacherAssignments.map((t) => t.classId))];

    if (classIds.length === 0) {
      return { assignedClasses: [], todaySchedules: [], myStudents: [] };
    }

    const assignedClasses = teacherAssignments.map((t) => ({
      classId: t.classId,
      className: t.class?.name || '',
      classCode: t.class?.code || '',
      roleType: t.roleType,
      subject: t.subject,
    }));

    // Get today's China business date and day of week (1=Monday, 7=Sunday)
    const { date: todayStr, weekDay: adjustedWeekDay } = getChinaDateParts();

    const todaySchedules = await this.classScheduleRepo
      .createQueryBuilder('cs')
      .where('cs.classId IN (:...classIds)', { classIds })
      .andWhere('cs.weekDay = :weekDay', { weekDay: adjustedWeekDay })
      .andWhere('cs.startDate <= :today', { today: todayStr })
      .andWhere('cs.endDate >= :today', { today: todayStr })
      .andWhere('cs.status = :status', { status: 'active' })
      .orderBy('cs.startTime', 'ASC')
      .getMany();

    const classStudents = await this.classStudentRepo.find({
      where: { classId: In(classIds), status: 'active' },
      relations: ['student', 'class'],
    });

    const myStudents = classStudents.map((cs) => ({
      studentId: cs.studentId,
      studentName: cs.student?.name || '',
      studentNo: cs.student?.studentNo || '',
      className: cs.class?.name || '',
      classId: cs.classId,
      joinDate: cs.joinDate,
    }));

    return {
      assignedClasses,
      todaySchedules: todaySchedules.map((s) => ({
        id: s.id,
        classId: s.classId,
        classroomId: s.classroomId,
        teacherId: s.teacherId,
        weekDay: s.weekDay,
        startTime: s.startTime,
        endTime: s.endTime,
        subject: s.subject,
        scheduleType: s.scheduleType,
      })),
      myStudents,
    };
  }
  async seedData(): Promise<void> {
    return this.seedOps.seedData();
  }

  async findAllUsers(isArchived = false) {
    return this.userOps.findAllUsers(isArchived);
  }

  async createUser(dto: { username: string; password: string; name: string; roleIds?: number[] }) {
    return this.userOps.createUser(dto);
  }

  async updateUser(id: number, dto: { username?: string; name?: string; roleIds?: number[] }) {
    return this.userOps.updateUser(id, dto);
  }

  async resetPassword(id: number, newPassword: string) {
    return this.userOps.resetPassword(id, newPassword);
  }

  async archiveUser(id: number) {
    return this.userOps.archiveUser(id);
  }

  async restoreUser(id: number) {
    return this.userOps.restoreUser(id);
  }

  async purgeUser(id: number, currentUserId: number) {
    return this.userOps.purgeUser(id, currentUserId);
  }

  async markAsStaff(userId: number) {
    return this.userOps.markAsStaff(userId);
  }

  async markAsStudent(userId: number) {
    return this.userOps.markAsStudent(userId);
  }

  async getUserProfile(id: number) {
    return this.userOps.getUserProfile(id);
  }

  async updateUserProfile(id: number, dto: UpdateProfileDto) {
    return this.userOps.updateUserProfile(id, dto);
  }

  async getTeachers(query?: { search?: string; page?: number; pageSize?: number }) {
    return this.userOps.getTeachers(query);
  }

  async updateTeacherProfile(id: number, dto: UpdateProfileDto) {
    return this.userOps.updateTeacherProfile(id, dto);
  }

}
