import { Organization } from '../entities/organization.entity';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { Occupancy } from '../entities/occupancy.entity';
import { buildPreflightReport } from './imports.preflight';
import type { ImportSheetData } from './imports.workbook';

function sheet(name: string, headers: string[], rows: unknown[][]): ImportSheetData {
  return { name, headers, rows: rows as ImportSheetData['rows'] };
}

function dataSourceOf(options: {
  students?: Student[];
  rooms?: Room[];
  organizations?: Organization[];
  occupancies?: Occupancy[];
} = {}) {
  return {
    getRepository: jest.fn((entity: unknown) => {
      if (entity === Student) return { find: jest.fn().mockResolvedValue(options.students ?? []) };
      if (entity === Room) return { find: jest.fn().mockResolvedValue(options.rooms ?? []) };
      if (entity === Organization) {
        return { find: jest.fn().mockResolvedValue(options.organizations ?? []) };
      }
      if (entity === Occupancy) {
        return { find: jest.fn().mockResolvedValue(options.occupancies ?? []) };
      }
      return { find: jest.fn().mockResolvedValue([]) };
    }),
  };
}

describe('buildPreflightReport', () => {
  it('全新学生表判定为 ready，给出分阶段统计与下一步建议', async () => {
    const report = await buildPreflightReport(
      dataSourceOf({ organizations: [{ id: 1, name: '主校区' }] }) as never,
      [
        sheet('学生', ['姓名', '学号', '手机号'], [
          ['张三', '2024001', '13800138000'],
          ['李四', '2024002', '13900139000'],
        ]),
      ],
    );

    expect(report.verdict).toBe('ready');
    expect(report.questions).toEqual([]);
    expect(report.stages).toHaveLength(1);
    expect(report.stages[0]).toMatchObject({
      stepKey: 'students',
      total: 2,
      create: 2,
      update: 0,
      error: 0,
      skip: 0,
      mapping: { name: '姓名', studentNo: '学号', phone: '手机号' },
    });
    expect(report.blocks).toEqual([]);
    expect(report.nextSteps.some((step) => step.key === 'students-next')).toBe(true);
  });

  it('已匹配记录时判定为 needs_input 并提出更新策略问题', async () => {
    const existing = {
      id: 88,
      name: '张三',
      studentNo: '2024001',
      phone: '13800138000',
    } as Student;
    const report = await buildPreflightReport(
      dataSourceOf({ students: [existing] }) as never,
      [sheet('学生', ['姓名', '学号'], [['张三', '2024001']])],
    );

    expect(report.verdict).toBe('needs_input');
    expect(report.stages[0]).toMatchObject({ total: 1, create: 0, update: 1 });
    expect(report.questions.some((question) => question.type === 'update')).toBe(true);
  });

  it('缺少必填列时判定为 blocked 并归因 missing_columns', async () => {
    const report = await buildPreflightReport(
      dataSourceOf() as never,
      [sheet('宿舍', ['宿舍号', '楼栋'], [['A101', '1号楼']])],
    );

    expect(report.verdict).toBe('blocked');
    expect(report.blocks).toContainEqual(
      expect.objectContaining({ code: 'missing_columns', count: 1, stepKeys: ['rooms'] }),
    );
    expect(report.stages[0].missingRequired).toContain('容量');
    expect(report.questions.some((question) => question.type === 'mapping')).toBe(true);
  });

  it('无法识别任何业务表时判定为 blocked', async () => {
    const report = await buildPreflightReport(
      dataSourceOf() as never,
      [sheet('杂项', ['A', 'B'], [['x', 'y']])],
    );

    expect(report.verdict).toBe('blocked');
    expect(report.blocks).toContainEqual(expect.objectContaining({ code: 'no_stages' }));
    expect(report.stages).toEqual([]);
  });

  it('文件内重复入住归因 duplicate_in_file 并提出重复策略问题', async () => {
    const student = {
      id: 88,
      name: '张三',
      studentNo: '2024001',
      phone: '13800138000',
    } as Student;
    const room = { id: 5, roomNumber: 'A101' } as Room;
    const report = await buildPreflightReport(
      dataSourceOf({ students: [student], rooms: [room] }) as never,
      [
        sheet('入住', ['姓名', '学号', '房间号', '入住日期'], [
          ['张三', '2024001', 'A101', '2026-09-01'],
          ['张三', '2024001', 'A101', '2026-09-02'],
        ]),
      ],
    );

    expect(report.verdict).toBe('needs_input');
    expect(report.stages[0]).toMatchObject({ stepKey: 'checkins', total: 2, create: 1, error: 1 });
    expect(report.blocks).toContainEqual(
      expect.objectContaining({ code: 'duplicate_in_file', count: 1, stepKeys: ['checkins'] }),
    );
    expect(report.questions.some((question) => question.type === 'duplicate')).toBe(true);
    expect(report.errorSamples).toContainEqual(
      expect.objectContaining({
        code: 'duplicate_in_file',
        stepKey: 'checkins',
        sheet: '入住',
        rowNumber: 3,
        errors: expect.arrayContaining([expect.stringContaining('请勿重复导入')]),
      }),
    );
  });

  it('未知校区归因 unknown_organization 并提出校区归属问题', async () => {
    const report = await buildPreflightReport(
      dataSourceOf({ organizations: [{ id: 1, name: '主校区' }] }) as never,
      [sheet('学生', ['姓名', '学号', '校区'], [['张三', '2024001', '东校区']])],
    );

    expect(report.verdict).toBe('needs_input');
    expect(report.blocks).toContainEqual(
      expect.objectContaining({ code: 'unknown_organization', count: 1 }),
    );
    const orgQuestion = report.questions.find((question) => question.type === 'organization');
    expect(orgQuestion).toBeDefined();
    expect(orgQuestion?.options?.map((option) => option.value)).toContain('主校区');
  });

  it('入住找不到学生/宿舍归因引用缺失并提出未匹配处理问题', async () => {
    const student = {
      id: 88,
      name: '张三',
      studentNo: '2024001',
      phone: '13800138000',
    } as Student;
    const report = await buildPreflightReport(
      dataSourceOf({ students: [student] }) as never,
      [
        sheet('入住', ['姓名', '学号', '房间号', '入住日期'], [
          ['张三', '2024001', 'A101', '2026-09-01'],
        ]),
      ],
    );

    expect(report.verdict).toBe('needs_input');
    expect(report.blocks).toContainEqual(
      expect.objectContaining({ code: 'room_not_found', count: 1, stepKeys: ['checkins'] }),
    );
    expect(report.questions.some((question) => question.type === 'reference')).toBe(true);
  });

  it('格式错误归因 format_error', async () => {
    const report = await buildPreflightReport(
      dataSourceOf() as never,
      [sheet('学生', ['姓名', '手机号'], [['张三', '123']])],
    );

    expect(report.blocks).toContainEqual(
      expect.objectContaining({ code: 'format_error', count: 1, stepKeys: ['students'] }),
    );
    expect(report.verdict).toBe('blocked');
    expect(report.errorSamples).toContainEqual(
      expect.objectContaining({
        code: 'format_error',
        stepKey: 'students',
        sheet: '学生',
        rowNumber: 2,
      }),
    );
  });
});
