import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as fs from 'fs';
import * as path from 'path';

import { NotificationsService } from '../notifications/notifications.service';
import { Student } from '../entities/student.entity';
import { StudentProfile } from '../entities/student-profile.entity';
import { StudentEnrollment } from '../entities/student-enrollment.entity';
import { ExamScore } from '../entities/exam-score.entity';
import { LearningRecord } from '../entities/learning-record.entity';
import { ResultArchive } from '../entities/result-archive.entity';
import { ArchiveAttachment } from '../entities/archive-attachment.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import {
  UpsertProfileDto,
  CreateEnrollmentDto,
  UpdateEnrollmentDto,
  CreateExamScoreDto,
  UpdateExamScoreDto,
  CreateLearningRecordDto,
  UpdateLearningRecordDto,
  UpsertResultDto,
} from './dto/archive.dto';

@Injectable()
export class ArchiveService {
  constructor(
    @InjectRepository(Student) private studentRepo: Repository<Student>,
    @InjectRepository(StudentProfile) private profileRepo: Repository<StudentProfile>,
    @InjectRepository(StudentEnrollment) private enrollmentRepo: Repository<StudentEnrollment>,
    @InjectRepository(ExamScore) private examScoreRepo: Repository<ExamScore>,
    @InjectRepository(LearningRecord) private learningRecordRepo: Repository<LearningRecord>,
    @InjectRepository(ResultArchive) private resultRepo: Repository<ResultArchive>,
    @InjectRepository(ArchiveAttachment) private attachmentRepo: Repository<ArchiveAttachment>,
    @InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
    private readonly notificationsService: NotificationsService,
  ) {}

  get uploadDir(): string {
    const base = process.env.UPLOAD_DIR || './uploads';
    return path.resolve(base, 'archive');
  }

  private resolveAttachmentPath(filePath: string): string {
    const normalizedPath = filePath.replace(/\\/g, '/');
    const fullPath = normalizedPath.startsWith('uploads/')
      ? path.resolve(process.cwd(), normalizedPath)
      : path.resolve(this.uploadDir, normalizedPath);
    const allowedRoots = [this.uploadDir, path.resolve(process.cwd(), 'uploads', 'archive')];
    if (
      !allowedRoots.some((root) => fullPath === root || fullPath.startsWith(`${root}${path.sep}`))
    ) {
      throw new BadRequestException('路径非法');
    }
    return fullPath;
  }

  async getProfile(studentId: number) {
    const student = await this.studentRepo.findOne({
      where: { id: studentId },
      relations: ['organization'],
    });
    if (!student) throw new NotFoundException('学生不存在');

    const [
      profileRaw,
      enrollments,
      examScores,
      learningRecords,
      resultArchive,
      attachments,
      attendances,
    ] = await Promise.all([
      this.profileRepo.findOne({ where: { studentId } }),
      this.enrollmentRepo.find({ where: { studentId, status: 'active' }, order: { createdAt: 'DESC' } }),
      this.examScoreRepo.find({
        where: { studentId, status: 'active' },
        relations: ['exam', 'exam.class'],
        order: { examDate: 'DESC' },
      }),
      this.learningRecordRepo.find({ where: { studentId, status: 'active' }, order: { recordDate: 'DESC' } }),
      this.resultRepo.findOne({ where: { studentId } }),
      this.attachmentRepo.find({ where: { studentId, status: 'active' }, order: { createdAt: 'DESC' } }),
      this.attendanceRepo.find({
        where: { studentId },
        relations: ['schedule', 'class'],
        order: { attendanceDate: 'DESC', punchTime: 'DESC' },
      }),
    ]);

    return {
      student,
      profile: profileRaw,
      enrollments,
      examScores,
      learningRecords,
      result: resultArchive,
      attachments,
      attendances,
    };
  }

  async upsertProfile(studentId: number, dto: UpsertProfileDto) {
    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    let profile = await this.profileRepo.findOne({ where: { studentId } });
    if (profile) {
      Object.assign(profile, dto);
    } else {
      profile = this.profileRepo.create({ ...dto, studentId });
    }
    return this.profileRepo.save(profile);
  }

  async addEnrollment(studentId: number, dto: CreateEnrollmentDto) {
    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    const entity = this.enrollmentRepo.create({ ...dto, studentId });
    return this.enrollmentRepo.save(entity);
  }

  async updateEnrollment(id: number, dto: UpdateEnrollmentDto) {
    const entity = await this.enrollmentRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('报名记录不存在');
    Object.assign(entity, dto);
    return this.enrollmentRepo.save(entity);
  }

  async deleteEnrollment(id: number) {
    const entity = await this.enrollmentRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('报名记录不存在');
    if (entity.status === 'archived') throw new BadRequestException('报名记录已归档');
    await this.enrollmentRepo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  private async assertEnrollmentBelongsToStudent(studentId: number, enrollmentId?: number) {
    if (enrollmentId === undefined) return;
    const enrollment = await this.enrollmentRepo.findOne({
      where: { id: enrollmentId, studentId },
    });
    if (!enrollment) throw new BadRequestException('报名记录不属于该学生');
  }

  async addExamScore(studentId: number, dto: CreateExamScoreDto) {
    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');
    await this.assertEnrollmentBelongsToStudent(studentId, dto.enrollmentId);

    const entity = this.examScoreRepo.create({ ...dto, studentId });
    return this.examScoreRepo.save(entity);
  }

  async updateExamScore(id: number, dto: UpdateExamScoreDto) {
    const entity = await this.examScoreRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('考试成绩不存在');
    if (entity.examId) throw new BadRequestException('考试管理同步成绩请在考试管理中修改');
    await this.assertEnrollmentBelongsToStudent(entity.studentId, dto.enrollmentId);
    Object.assign(entity, dto);
    return this.examScoreRepo.save(entity);
  }

  async deleteExamScore(id: number) {
    const entity = await this.examScoreRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('考试成绩不存在');
    if (entity.examId) throw new BadRequestException('考试管理同步成绩不能在学生档案中归档');
    if (entity.status === 'archived') throw new BadRequestException('考试成绩已归档');
    await this.examScoreRepo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  async addLearningRecord(studentId: number, dto: CreateLearningRecordDto) {
    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    const entity = this.learningRecordRepo.create({ ...dto, studentId });
    return this.learningRecordRepo.save(entity);
  }

  async updateLearningRecord(id: number, dto: UpdateLearningRecordDto) {
    const entity = await this.learningRecordRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('学习记录不存在');
    Object.assign(entity, dto);
    return this.learningRecordRepo.save(entity);
  }

  async deleteLearningRecord(id: number) {
    const entity = await this.learningRecordRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('学习记录不存在');
    if (entity.status === 'archived') throw new BadRequestException('学习记录已归档');
    await this.learningRecordRepo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  async upsertResult(studentId: number, dto: UpsertResultDto) {
    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    let result = await this.resultRepo.findOne({ where: { studentId } });
    if (result) {
      Object.assign(result, dto);
    } else {
      result = this.resultRepo.create({ ...dto, studentId });
    }
    return this.resultRepo.save(result);
  }

  async addAttachment(studentId: number, file: Express.Multer.File, category: string) {
    if (!file?.buffer || !file.originalname) throw new BadRequestException('请选择附件文件');

    const student = await this.studentRepo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    const uploadDir = this.uploadDir;
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true });
    }

    const ext = path.extname(file.originalname);
    const filename = `${studentId}_${Date.now()}${ext}`;
    const filePath = path.join(uploadDir, filename);
    fs.writeFileSync(filePath, file.buffer);

    const entity = this.attachmentRepo.create({
      studentId,
      category,
      fileName: file.originalname,
      filePath: filename,
      fileSize: file.size,
      mimeType: file.mimetype,
    });
    return this.attachmentRepo.save(entity);
  }

  async getAttachmentFile(studentId: number, id: number) {
    const entity = await this.attachmentRepo.findOne({ where: { id, studentId } });
    if (!entity) throw new NotFoundException('附件不存在');

    const fullPath = this.resolveAttachmentPath(entity.filePath);
    if (!fs.existsSync(fullPath)) throw new NotFoundException('附件文件丢失');

    return {
      fullPath,
      fileName: entity.fileName || path.basename(fullPath),
      mimeType: entity.mimeType || 'application/octet-stream',
    };
  }

  async deleteAttachment(id: number) {
    const entity = await this.attachmentRepo.findOne({ where: { id } });
    if (!entity) throw new NotFoundException('附件不存在');
    if (entity.status === 'archived') throw new BadRequestException('附件已归档');
    await this.attachmentRepo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }
}
