import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
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 { Organization } from '../entities/organization.entity';
import type {
  ExamScoreImportRow,
  LearningRecordImportRow,
  StudentEnrollmentImportRow,
  StudentImportRow,
  StudentWorkbookImport,
} from './student-import';
import { getHostOrganizationId } from './students.organization';

@Injectable()
export class StudentsImportService {
  constructor(
    @InjectRepository(Student) private readonly repo: Repository<Student>,
    @InjectRepository(StudentProfile) private readonly profileRepo: Repository<StudentProfile>,
    @InjectRepository(StudentEnrollment)
    private readonly enrollmentRepo: Repository<StudentEnrollment>,
    @InjectRepository(ExamScore) private readonly examScoreRepo: Repository<ExamScore>,
    @InjectRepository(LearningRecord)
    private readonly learningRecordRepo: Repository<LearningRecord>,
    @InjectRepository(ResultArchive) private readonly resultRepo: Repository<ResultArchive>,
    @InjectRepository(Organization) private readonly organizationRepo: Repository<Organization>,
  ) {}

  async batchImport(importData: StudentWorkbookImport | StudentImportRow[]) {
    const data = this.normalizeImportData(importData);
    let imported = 0;
    let skipped = 0;
    let archiveImported = 0;
    for (const row of data.students) {
      if (!row.name || !row.name.trim()) {
        skipped++;
        continue;
      }
      const exists = await this.repo.findOne({ where: { name: row.name.trim() } });
      if (exists) {
        skipped++;
        continue;
      }
      const student = await this.repo.save(
        this.repo.create({
          name: row.name.trim(),
          studentNo: row.studentNo?.trim() || undefined,
          phone: row.phone?.trim() || undefined,
          idNumber: row.idNumber?.trim() || undefined,
          gender: row.gender || undefined,
          ethnicity: row.ethnicity || undefined,
          emergencyContact: row.emergencyContact || undefined,
          emergencyPhone: row.emergencyPhone || undefined,
          supervisor: row.supervisor || undefined,
          organizationId: row.organizationId || (await getHostOrganizationId(this.organizationRepo)),
        }),
      );
      archiveImported += await this.importArchiveData(student.id, row, data);
      imported++;
    }
    return {
      message: `成功导入 ${imported} 名学生，导入档案相关记录 ${archiveImported} 条，跳过 ${skipped} 条（重复或空行）`,
      imported,
      archiveImported,
      skipped,
    };
  }

  async matchImport(importData: StudentWorkbookImport | StudentImportRow[]) {
    const data = this.normalizeImportData(importData);
    let matched = 0;
    let skipped = 0;
    let archiveImported = 0;
    for (const row of data.students) {
      // Match by phone first, then idNumber
      let student = row.phone?.trim()
        ? await this.repo.findOne({ where: { phone: row.phone.trim() } })
        : null;
      if (!student && row.idNumber?.trim()) {
        student = await this.repo.findOne({ where: { idNumber: row.idNumber.trim() } });
      }
      if (!student) {
        skipped++;
        continue;
      }
      const updates: Partial<
        Pick<
          Student,
          | 'name'
          | 'studentNo'
          | 'phone'
          | 'idNumber'
          | 'gender'
          | 'ethnicity'
          | 'emergencyContact'
          | 'emergencyPhone'
          | 'supervisor'
          | 'organizationId'
        >
      > = {};
      if (row.name?.trim()) updates.name = row.name.trim();
      if (row.studentNo?.trim()) updates.studentNo = row.studentNo.trim();
      if (row.phone?.trim()) updates.phone = row.phone.trim();
      if (row.idNumber?.trim()) updates.idNumber = row.idNumber.trim();
      if (row.gender) updates.gender = row.gender;
      if (row.ethnicity) updates.ethnicity = row.ethnicity;
      if (row.emergencyContact) updates.emergencyContact = row.emergencyContact;
      if (row.emergencyPhone) updates.emergencyPhone = row.emergencyPhone;
      if (row.supervisor) updates.supervisor = row.supervisor;
      if (row.organizationId) updates.organizationId = row.organizationId;
      await this.repo.update(student.id, updates);
      archiveImported += await this.importArchiveData(student.id, row, data);
      matched++;
    }
    return {
      message: `更新已有学生资料 ${matched} 人，导入档案相关记录 ${archiveImported} 条，跳过 ${skipped} 条（无匹配）`,
      matched,
      archiveImported,
      skipped,
    };
  }

  private normalizeImportData(
    importData: StudentWorkbookImport | StudentImportRow[],
  ): StudentWorkbookImport {
    if (Array.isArray(importData)) {
      return { students: importData, enrollments: [], examScores: [], learningRecords: [] };
    }
    return importData;
  }

  private normalizePhone(phone?: string) {
    return phone?.trim() || '';
  }

  private sameValue(left?: string | number | null, right?: string | number | null) {
    return String(left ?? '').trim() === String(right ?? '').trim();
  }

  private hasProfileData(row: StudentImportRow) {
    return [
      row.targetCollege,
      row.targetMajor,
      row.collegeSchool,
      row.collegeMajor,
      row.subjectDirection,
      row.grade,
      row.profileDate,
      row.notes,
    ].some((value) => value !== undefined && String(value).trim() !== '');
  }

  private hasResultData(row: StudentImportRow) {
    return [
      row.cultureFinalScore,
      row.professionalFinalScore,
      row.admissionStatus,
      row.admittedCollege,
      row.admittedMajor,
    ].some((value) => value !== undefined && String(value).trim() !== '');
  }

  private async importArchiveData(
    studentId: number,
    row: StudentImportRow,
    data: StudentWorkbookImport,
  ) {
    const phone = this.normalizePhone(row.phone);
    let imported = 0;
    if (this.hasProfileData(row)) {
      await this.upsertProfileFromImport(studentId, row);
      imported++;
    }
    if (this.hasResultData(row)) {
      await this.upsertResultFromImport(studentId, row);
      imported++;
    }
    if (!phone) return imported;

    const enrollmentByClassName = new Map<string, StudentEnrollment>();
    for (const enrollmentRow of data.enrollments.filter(
      (item) => this.normalizePhone(item.phone) === phone,
    )) {
      const enrollment = await this.upsertEnrollmentFromImport(studentId, enrollmentRow);
      if (!enrollment) continue;
      if (enrollment.className) enrollmentByClassName.set(enrollment.className, enrollment);
      imported++;
    }
    for (const examRow of data.examScores.filter(
      (item) => this.normalizePhone(item.phone) === phone,
    )) {
      if (await this.upsertExamScoreFromImport(studentId, examRow, enrollmentByClassName)) {
        imported++;
      }
    }
    for (const learningRow of data.learningRecords.filter(
      (item) => this.normalizePhone(item.phone) === phone,
    )) {
      if (await this.upsertLearningRecordFromImport(studentId, learningRow)) {
        imported++;
      }
    }
    return imported;
  }

  private async upsertProfileFromImport(studentId: number, row: StudentImportRow) {
    const entity =
      (await this.profileRepo.findOne({ where: { studentId } })) ||
      this.profileRepo.create({ studentId });
    if (row.targetCollege?.trim()) entity.targetCollege = row.targetCollege.trim();
    if (row.targetMajor?.trim()) entity.targetMajor = row.targetMajor.trim();
    if (row.collegeSchool?.trim()) entity.collegeSchool = row.collegeSchool.trim();
    if (row.collegeMajor?.trim()) entity.collegeMajor = row.collegeMajor.trim();
    if (row.subjectDirection?.trim()) entity.subjectDirection = row.subjectDirection.trim();
    if (row.grade?.trim()) entity.grade = row.grade.trim();
    if (row.profileDate?.trim()) entity.profileDate = row.profileDate.trim();
    if (row.notes?.trim()) entity.notes = row.notes.trim();
    await this.profileRepo.save(entity);
  }

  private async upsertResultFromImport(studentId: number, row: StudentImportRow) {
    const entity =
      (await this.resultRepo.findOne({ where: { studentId } })) ||
      this.resultRepo.create({ studentId });
    if (row.cultureFinalScore !== undefined) entity.cultureFinalScore = row.cultureFinalScore;
    if (row.professionalFinalScore !== undefined)
      entity.professionalFinalScore = row.professionalFinalScore;
    if (row.admissionStatus?.trim()) entity.admissionStatus = row.admissionStatus.trim();
    if (row.admittedCollege?.trim()) entity.admittedCollege = row.admittedCollege.trim();
    if (row.admittedMajor?.trim()) entity.admittedMajor = row.admittedMajor.trim();
    await this.resultRepo.save(entity);
  }

  private async upsertEnrollmentFromImport(studentId: number, row: StudentEnrollmentImportRow) {
    if (!row.courseCategory?.trim() || !row.classType?.trim()) {
      return null;
    }
    const existing = await this.enrollmentRepo.find({ where: { studentId } });
    const entity =
      existing.find(
        (item) =>
          this.sameValue(item.courseCategory, row.courseCategory) &&
          this.sameValue(item.classType, row.classType) &&
          this.sameValue(item.className, row.className) &&
          this.sameValue(item.startDate, row.startDate),
      ) || this.enrollmentRepo.create({ studentId });
    entity.courseCategory = row.courseCategory.trim();
    entity.classType = row.classType.trim();
    if (row.className?.trim()) entity.className = row.className.trim();
    if (row.headTeacher?.trim()) entity.headTeacher = row.headTeacher.trim();
    if (row.subjectTeacher?.trim()) entity.subjectTeacher = row.subjectTeacher.trim();
    if (row.startDate?.trim()) entity.startDate = row.startDate.trim();
    if (row.endDate?.trim()) entity.endDate = row.endDate.trim();
    if (row.status?.trim()) entity.status = row.status.trim();
    else if (!entity.status) entity.status = 'active';
    return this.enrollmentRepo.save(entity);
  }

  private async upsertExamScoreFromImport(
    studentId: number,
    row: ExamScoreImportRow,
    enrollmentByClassName: Map<string, StudentEnrollment>,
  ) {
    if (!row.examType?.trim() || !row.subject?.trim() || row.score === undefined) return false;
    const existing = await this.examScoreRepo.find({ where: { studentId } });
    const entity =
      existing.find(
        (item) =>
          this.sameValue(item.examType, row.examType) &&
          this.sameValue(item.examName, row.examName) &&
          this.sameValue(item.subject, row.subject) &&
          this.sameValue(item.examDate, row.examDate),
      ) || this.examScoreRepo.create({ studentId });
    entity.examType = row.examType.trim();
    entity.subject = row.subject.trim();
    entity.score = row.score;
    if (row.examName?.trim()) entity.examName = row.examName.trim();
    if (row.classAvg !== undefined) entity.classAvg = row.classAvg;
    if (row.rank !== undefined) entity.rank = row.rank;
    if (row.examDate?.trim()) entity.examDate = row.examDate.trim();
    if (row.enrollmentName?.trim()) {
      const enrollment = enrollmentByClassName.get(row.enrollmentName.trim());
      if (enrollment) entity.enrollmentId = enrollment.id;
    }
    if (!entity.status) entity.status = 'active';
    await this.examScoreRepo.save(entity);
    return true;
  }

  private async upsertLearningRecordFromImport(studentId: number, row: LearningRecordImportRow) {
    if (!row.recordDate?.trim() || !row.recordType?.trim() || !row.content?.trim()) return false;
    const existing = await this.learningRecordRepo.find({ where: { studentId } });
    const entity =
      existing.find(
        (item) =>
          this.sameValue(item.recordDate, row.recordDate) &&
          this.sameValue(item.recordType, row.recordType) &&
          this.sameValue(item.content, row.content),
      ) || this.learningRecordRepo.create({ studentId });
    entity.recordDate = row.recordDate.trim();
    entity.recordType = row.recordType.trim();
    entity.content = row.content.trim();
    if (row.followUpMethod?.trim()) entity.followUpMethod = row.followUpMethod.trim();
    if (row.nextStep?.trim()) entity.nextStep = row.nextStep.trim();
    if (!entity.status) entity.status = 'active';
    await this.learningRecordRepo.save(entity);
    return true;
  }
}
