import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ForbiddenException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository, In, Like } from 'typeorm';
import {
  Class,
  ClassStudent,
  ClassTeacher,
  ClassSchedule,
  AttendanceRecord,
  AttendanceSession,
  Classroom,
  Student,
  StudentDingMapping,
} from '../entities';
import { syncDingTalkStudents } from '../integration/dingtalk-student-sync';
import { normalizeDateOnly } from '../database/date-normalization';
import {
  CreateClassDto,
  UpdateClassDto,
  QueryClassDto,
  AddTeacherDto,
  QueryClassScheduleDto,
  QueryClassAttendanceSummaryDto,
} from './dto/class.dto';

interface RawStudentCount {
  classId: string;
  count: string;
}

@Injectable()
export class ClassesService {
  constructor(
    @InjectRepository(Class)
    private classRepo: Repository<Class>,
    @InjectRepository(ClassStudent)
    private classStudentRepo: Repository<ClassStudent>,
    @InjectRepository(ClassTeacher)
    private classTeacherRepo: Repository<ClassTeacher>,
    @InjectRepository(ClassSchedule)
    private scheduleRepo: Repository<ClassSchedule>,
    @InjectRepository(AttendanceRecord)
    private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(AttendanceSession)
    private attendanceSessionRepo: Repository<AttendanceSession>,
    @InjectRepository(Student)
    private studentRepo: Repository<Student>,
    @InjectRepository(StudentDingMapping)
    private studentDingMappingRepo: Repository<StudentDingMapping>,
    private dataSource: DataSource,
  ) {}

  async getAccessibleClassIds(userId: number, canManageAll = false): Promise<number[] | undefined> {
    if (canManageAll) return undefined;
    const assignments = await this.classTeacherRepo.find({ where: { userId } });
    return [...new Set(assignments.map((assignment) => assignment.classId))];
  }

  async assertClassAccess(userId: number, classId: number, canManageAll = false): Promise<void> {
    if (canManageAll) return;
    const assignment = await this.classTeacherRepo.findOne({ where: { userId, classId } });
    if (!assignment) throw new ForbiddenException('只能访问自己被分配的班级');
  }

  async findAll(query: QueryClassDto, accessibleClassIds?: number[]) {
    const where: Record<string, unknown> = {};
    if (query.status) where.status = query.status;
    if (query.classType) where.classType = query.classType;
    if (query.keyword) where.name = Like(`%${query.keyword}%`);
    // Default: hide archived, unless explicitly requested
    where.isArchived = query.isArchived ?? false;

    if (accessibleClassIds) {
      if (accessibleClassIds.length === 0) return [];
      where.id = In(accessibleClassIds);
    }

    const classes = await this.classRepo.find({
      where,
      order: { createdAt: 'DESC' as const },
    });

    // count students per class
    const studentCounts: RawStudentCount[] = await this.classStudentRepo
      .createQueryBuilder('cs')
      .select('cs.class_id', 'classId')
      .addSelect('COUNT(cs.id)', 'count')
      .where('cs.status = :status', { status: 'active' })
      .groupBy('cs.class_id')
      .getRawMany();

    const countMap = new Map(studentCounts.map((r) => [Number(r.classId), Number(r.count)]));

    return classes.map((c) => ({
      ...c,
      studentCount: countMap.get(c.id) || 0,
    }));
  }

  async findOne(id: number) {
    const cls = await this.classRepo.findOne({ where: { id } });
    if (!cls) throw new NotFoundException('班级不存在');

    const students = await this.classStudentRepo.find({
      where: { classId: id },
      relations: ['student'],
    });
    const teachers = await this.classTeacherRepo.find({
      where: { classId: id },
      relations: ['user'],
    });

    return {
      ...cls,
      students: students.map((s) => ({
        id: s.id,
        studentId: s.studentId,
        studentName: s.student?.name,
        studentNo: s.student?.studentNo,
        joinDate: s.joinDate,
        leaveDate: s.leaveDate,
        status: s.status,
      })),
      teachers: teachers.map((t) => ({
        id: t.id,
        userId: t.userId,
        username: t.user?.username,
        roleType: t.roleType,
        subject: t.subject,
      })),
      studentCount: students.filter((s) => s.status === 'active').length,
    };
  }

  async create(dto: CreateClassDto) {
    const { studentIds, teachers, users, ...classData } = dto;

    const cls = this.classRepo.create({
      ...classData,
      startDate: normalizeDateOnly(classData.startDate) ?? undefined,
      endDate: normalizeDateOnly(classData.endDate) ?? undefined,
    });
    const saved = await this.classRepo.save(cls);

    // add students
    if (studentIds?.length) {
      const entries = studentIds.map((sid: number) =>
        this.classStudentRepo.create({
          classId: saved.id,
          studentId: sid,
          joinDate: new Date().toISOString().split('T')[0],
        }),
      );
      await this.classStudentRepo.save(entries);
    }

    // add teachers
    if (teachers?.length) {
      const entries = teachers.map((t) =>
        this.classTeacherRepo.create({
          classId: saved.id,
          userId: t.userId,
          roleType: t.roleType,
          subject: t.subject,
        }),
      );
      await this.classTeacherRepo.save(entries);

      // sync head/life/academic teacher IDs
      await this.syncClassTeacherIds(saved.id);
    }

    // batch import students by dingUserIds
    if (users?.length) {
      await this.batchImportStudents(saved.id, users);
    }

    return this.findOne(saved.id);
  }

  async batchImportStudents(
    classId: number,
    users: Array<{
      dingUserId: string;
      name: string;
      mobile?: string;
    }>,
  ): Promise<{ imported: number; skipped: number; conflicts: number }> {
    if (users.length === 0) return { imported: 0, skipped: 0, conflicts: 0 };

    return this.dataSource.transaction(async (manager) => {
      const classEntity = await manager.findOne(Class, { where: { id: classId } });
      if (!classEntity) throw new NotFoundException('班级不存在');

      const synced = await syncDingTalkStudents(manager, users);
      const studentIds = [...new Set(synced.studentIds.values())];
      if (studentIds.length === 0) {
        return { imported: 0, skipped: 0, conflicts: synced.conflicts.length };
      }

      const existingClassStudents = await manager.find(ClassStudent, {
        where: { classId, studentId: In(studentIds) },
      });
      const existingByStudentId = new Map(
        existingClassStudents.map((classStudent) => [classStudent.studentId, classStudent]),
      );
      const today = new Date().toISOString().slice(0, 10);
      let skipped = 0;
      const memberships = studentIds.flatMap((studentId) => {
        const existing = existingByStudentId.get(studentId);
        if (existing?.status === 'active') {
          skipped++;
          return [];
        }
        if (existing) {
          existing.status = 'active';
          existing.joinDate = today;
          existing.leaveDate = null;
          return [existing];
        }
        return [
          manager.create(ClassStudent, {
            classId,
            studentId,
            status: 'active',
            joinDate: today,
          }),
        ];
      });

      if (memberships.length > 0) await manager.save(ClassStudent, memberships);
      return {
        imported: memberships.length,
        skipped,
        conflicts: synced.conflicts.length,
      };
    });
  }
  async update(id: number, dto: UpdateClassDto) {
    const cls = await this.classRepo.findOne({ where: { id } });
    if (!cls) throw new NotFoundException('班级不存在');
    await this.classRepo.update(id, {
      ...dto,
      ...(dto.startDate !== undefined
        ? { startDate: normalizeDateOnly(dto.startDate) ?? undefined }
        : {}),
      ...(dto.endDate !== undefined
        ? { endDate: normalizeDateOnly(dto.endDate) ?? undefined }
        : {}),
    });
    return this.findOne(id);
  }

  /** 归档班级（软删除） */
  async archive(id: number) {
    const cls = await this.classRepo.findOne({ where: { id } });
    if (!cls) throw new NotFoundException('班级不存在');
    if (cls.isArchived) throw new BadRequestException('班级已归档');
    await this.classRepo.update(id, { isArchived: true });
    return { success: true };
  }

  /** 取消归档 */
  async restore(id: number) {
    const cls = await this.classRepo.findOne({ where: { id } });
    if (!cls) throw new NotFoundException('班级不存在');
    if (!cls.isArchived) throw new BadRequestException('班级未归档');
    await this.classRepo.update(id, { isArchived: false });
    return { success: true };
  }

  /** 归档班级（兼容旧删除入口，不物理删除） */
  async remove(id: number) {
    return this.archive(id);
  }

  async getStudents(classId: number) {
    return this.classStudentRepo.find({
      where: { classId },
      relations: ['student'],
      order: { createdAt: 'ASC' as const },
    });
  }

  async addStudents(classId: number, studentIds: number[]) {
    const uniqueStudentIds = [...new Set(studentIds)];
    if (uniqueStudentIds.length === 0) return { added: 0, skipped: 0 };

    const cls = await this.classRepo.findOne({ where: { id: classId } });
    if (!cls) throw new NotFoundException('班级不存在');

    const students = await this.studentRepo.find({ where: { id: In(uniqueStudentIds) } });
    if (students.length !== uniqueStudentIds.length) {
      throw new NotFoundException('部分学生不存在');
    }

    const existing = await this.classStudentRepo.find({
      where: { classId, studentId: In(uniqueStudentIds) },
    });
    const existingByStudentId = new Map(
      existing.map((classStudent) => [classStudent.studentId, classStudent]),
    );
    const today = new Date().toISOString().split('T')[0];
    let skipped = 0;
    const memberships = uniqueStudentIds.flatMap((studentId) => {
      const current = existingByStudentId.get(studentId);
      if (current?.status === 'active') {
        skipped++;
        return [];
      }
      if (current) {
        current.status = 'active';
        current.joinDate = today;
        current.leaveDate = null;
        return [current];
      }
      return [
        this.classStudentRepo.create({
          classId,
          studentId,
          status: 'active',
          joinDate: today,
        }),
      ];
    });
    if (memberships.length) await this.classStudentRepo.save(memberships);

    return { added: memberships.length, skipped };
  }

  async removeStudent(classId: number, studentId: number) {
    const membership = await this.classStudentRepo.findOne({
      where: { classId, studentId },
    });
    if (!membership) throw new NotFoundException('学生不在该班级');
    if (membership.status !== 'active') throw new BadRequestException('学生已离班');

    membership.status = 'left';
    membership.leaveDate = new Date().toISOString().split('T')[0];
    await this.classStudentRepo.save(membership);
    return { success: true };
  }

  async getTeachers(classId: number) {
    return this.classTeacherRepo.find({
      where: { classId },
      relations: ['user'],
    });
  }

  async addTeacher(classId: number, dto: AddTeacherDto) {
    const cls = await this.classRepo.findOne({ where: { id: classId } });
    if (!cls) throw new NotFoundException('班级不存在');

    const existing = await this.classTeacherRepo.findOne({
      where: { classId, userId: dto.userId, roleType: dto.roleType },
    });
    if (existing) throw new BadRequestException('该教师已分配此角色');

    const entry = this.classTeacherRepo.create({
      classId,
      userId: dto.userId,
      roleType: dto.roleType,
      subject: dto.subject,
    });
    await this.classTeacherRepo.save(entry);

    await this.syncClassTeacherIds(classId);
    return entry;
  }

  async removeTeacher(classId: number, userId: number) {
    const assignments = await this.classTeacherRepo.find({ where: { classId, userId } });
    if (assignments.length === 0) throw new NotFoundException('教师未分配到该班级');
    await this.classTeacherRepo.delete({ classId, userId });
    await this.syncClassTeacherIds(classId);
    return { success: true };
  }

  async removeTeacherAssignment(classId: number, assignmentId: number) {
    const assignment = await this.classTeacherRepo.findOne({
      where: { id: assignmentId, classId },
    });
    if (!assignment) throw new NotFoundException('教师角色分配不存在');
    await this.classTeacherRepo.delete({ id: assignmentId, classId });
    await this.syncClassTeacherIds(classId);
    return { success: true };
  }

  private async syncClassTeacherIds(classId: number) {
    const teachers = await this.classTeacherRepo.find({ where: { classId } });
    const head = teachers.find((t) => t.roleType === 'head_teacher');
    const life = teachers.find((t) => t.roleType === 'life_teacher');
    const academic = teachers.find((t) => t.roleType === 'academic_teacher');
    await this.classRepo.update(classId, {
      headTeacherId: head?.userId ?? null,
      lifeTeacherId: life?.userId ?? null,
      academicTeacherId: academic?.userId ?? null,
    } as Partial<Class>);
  }

  async getSchedule(classId: number, query: QueryClassScheduleDto) {
    const qb = this.scheduleRepo
      .createQueryBuilder('cs')
      .leftJoinAndSelect('cs.classroom', 'classroom')
      .where('cs.classId = :classId', { classId });

    if (query.startDate) {
      qb.andWhere('cs.endDate >= :startDate', { startDate: query.startDate });
    }
    if (query.endDate) {
      qb.andWhere('cs.startDate <= :endDate', { endDate: query.endDate });
    }

    const schedules = await qb
      .orderBy('cs.weekDay', 'ASC')
      .addOrderBy('cs.startTime', 'ASC')
      .getMany();

    return schedules.map((s) => ({
      ...s,
      classroomName: (s.classroom as Classroom | undefined)?.name || null,
    }));
  }

  async getAttendanceSummary(classId: number, query: QueryClassAttendanceSummaryDto) {
    const qb = this.attendanceRepo
      .createQueryBuilder('ar')
      .where('ar.classId = :classId', { classId });

    if (query.startDate) {
      qb.andWhere('ar.attendanceDate >= :startDate', { startDate: query.startDate });
    }
    if (query.endDate) {
      qb.andWhere('ar.attendanceDate <= :endDate', { endDate: query.endDate });
    }

    const rows = await qb.getMany();

    const total = rows.length;
    const present = rows.filter((r) => r.status === 'present').length;
    const late = rows.filter((r) => r.status === 'late').length;
    const absent = rows.filter((r) => r.status === 'absent').length;
    const leave = rows.filter((r) => r.status === 'leave').length;

    return {
      total,
      present,
      late,
      absent,
      leave,
      presentRate: total > 0 ? Number(((present / total) * 100).toFixed(1)) : 0,
      absentRate: total > 0 ? Number(((absent / total) * 100).toFixed(1)) : 0,
      lateRate: total > 0 ? Number(((late / total) * 100).toFixed(1)) : 0,
      leaveRate: total > 0 ? Number(((leave / total) * 100).toFixed(1)) : 0,
    };
  }
}
