import { BadRequestException, NotFoundException } from '@nestjs/common';
import { ArchiveService } from './archive.service';

function createService(repos: Partial<Record<string, Record<string, jest.Mock>>> = {}) {
  return new ArchiveService(
    (repos.student ?? {}) as never,
    (repos.profile ?? {}) as never,
    (repos.enrollment ?? {}) as never,
    (repos.exam ?? {}) as never,
    (repos.learning ?? {}) as never,
    (repos.result ?? {}) as never,
    (repos.attachment ?? {}) as never,
    (repos.attendance ?? {}) as never,
    {} as never,
  );
}

describe('ArchiveService — resource and relationship boundaries', () => {
  it('rejects adding archive records for a missing student', async () => {
    const student = { findOne: jest.fn().mockResolvedValue(null) };
    const service = createService({ student });

    await expect(
      service.addEnrollment(404, { courseCategory: '文化', classType: '冲刺' }),
    ).rejects.toBeInstanceOf(NotFoundException);
    await expect(
      service.addLearningRecord(404, {
        recordDate: '2026-07-14',
        recordType: '沟通',
        content: '内容',
      }),
    ).rejects.toBeInstanceOf(NotFoundException);
  });

  it('rejects linking an exam score to another student enrollment', async () => {
    const exam = { create: jest.fn(), save: jest.fn() };
    const service = createService({
      student: { findOne: jest.fn().mockResolvedValue({ id: 7 }) },
      enrollment: { findOne: jest.fn().mockResolvedValue(null) },
      exam,
    });

    await expect(
      service.addExamScore(7, {
        examType: '月考',
        subject: '语文',
        score: 90,
        enrollmentId: 99,
      }),
    ).rejects.toBeInstanceOf(BadRequestException);
    expect(exam.save).not.toHaveBeenCalled();
  });

  it('rejects moving an existing exam score to another student enrollment', async () => {
    const exam = {
      findOne: jest.fn().mockResolvedValue({ id: 3, studentId: 7, enrollmentId: 1 }),
      save: jest.fn(),
    };
    const service = createService({
      enrollment: { findOne: jest.fn().mockResolvedValue(null) },
      exam,
    });

    await expect(service.updateExamScore(3, { enrollmentId: 99 })).rejects.toBeInstanceOf(
      BadRequestException,
    );
    expect(exam.save).not.toHaveBeenCalled();
  });

  it('keeps exam-management scores read-only in the student archive', async () => {
    const exam = {
      findOne: jest.fn().mockResolvedValue({ id: 3, studentId: 7, examId: 8 }),
      save: jest.fn(),
      update: jest.fn(),
    };
    const service = createService({ exam });

    await expect(service.updateExamScore(3, { score: 95 })).rejects.toBeInstanceOf(
      BadRequestException,
    );
    await expect(service.deleteExamScore(3)).rejects.toBeInstanceOf(BadRequestException);
    expect(exam.save).not.toHaveBeenCalled();
    expect(exam.update).not.toHaveBeenCalled();
  });

  it('rejects a missing attachment upload before writing to disk', async () => {
    const service = createService({ student: { findOne: jest.fn() } });
    await expect(service.addAttachment(7, undefined as never, 'other')).rejects.toBeInstanceOf(
      BadRequestException,
    );
  });

  it('rejects attachment path traversal', async () => {
    const service = createService({
      attachment: {
        findOne: jest.fn().mockResolvedValue({
          id: 1,
          studentId: 7,
          filePath: '../../etc/passwd',
        }),
      },
    });
    await expect(service.getAttachmentFile(7, 1)).rejects.toBeInstanceOf(BadRequestException);
  });

  it('returns not found for update/delete of absent child records', async () => {
    const service = createService({
      enrollment: { findOne: jest.fn().mockResolvedValue(null) },
      exam: { findOne: jest.fn().mockResolvedValue(null) },
      learning: { findOne: jest.fn().mockResolvedValue(null) },
      attachment: { findOne: jest.fn().mockResolvedValue(null) },
    });
    await expect(service.updateEnrollment(1, {})).rejects.toBeInstanceOf(NotFoundException);
    await expect(service.deleteExamScore(1)).rejects.toBeInstanceOf(NotFoundException);
    await expect(service.deleteLearningRecord(1)).rejects.toBeInstanceOf(NotFoundException);
    await expect(service.deleteAttachment(1)).rejects.toBeInstanceOf(NotFoundException);
  });
});
