import { DataSource, In } from 'typeorm';
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 { textValue } from './imports.helpers';
import type { CellValue, ColumnMapping, ImportStepKey } from './imports.types';

export interface ImportLookups {
  studentsByNo: Map<string, Student>;
  studentsByPhone: Map<string, Student>;
  roomsByNumber: Map<string, Room>;
  activeOccupancies: Map<number, Occupancy[]>;
  organizations: Map<string, Organization>;
}

export async function buildLookups(
  dataSource: DataSource,
  stepKey: ImportStepKey,
  headers: string[],
  rows: CellValue[][],
  mapping: ColumnMapping,
): Promise<ImportLookups> {
  const studentNos = new Set<string>();
  const phones = new Set<string>();
  const roomNumbers = new Set<string>();
  const organizationNames = new Set<string>();
  const fieldIndex: Record<string, number> = {};
  for (const [field, header] of Object.entries(mapping)) {
    const index = headers.indexOf(header);
    if (index >= 0) fieldIndex[field] = index;
  }
  const valueOf = (row: CellValue[], field: string): CellValue => {
    const index = fieldIndex[field];
    return index === undefined ? null : (row[index] ?? null);
  };
  for (const row of rows) {
    if (stepKey === 'students' || stepKey === 'checkins' || stepKey === 'transfers') {
      const no = textValue(valueOf(row, 'studentNo'));
      if (no) studentNos.add(no);
      const phone = textValue(valueOf(row, 'phone'));
      if (phone) phones.add(phone);
    }
    if (stepKey === 'rooms' || stepKey === 'checkins' || stepKey === 'transfers') {
      const roomField = stepKey === 'transfers' ? 'oldRoom' : 'roomNumber';
      const newRoomField = stepKey === 'transfers' ? 'newRoom' : undefined;
      const roomNo = textValue(valueOf(row, roomField));
      if (roomNo) roomNumbers.add(roomNo);
      if (newRoomField) {
        const newRoomNo = textValue(valueOf(row, newRoomField));
        if (newRoomNo) roomNumbers.add(newRoomNo);
      }
    }
    if (stepKey === 'students') {
      const org = textValue(valueOf(row, 'organization'));
      if (org) organizationNames.add(org);
    }
  }

  const students: Student[] = [];
  if (studentNos.size > 0) {
    students.push(
      ...(await dataSource.getRepository(Student).find({
        where: { studentNo: In([...studentNos]) },
      })),
    );
  }
  if (phones.size > 0) {
    students.push(
      ...(await dataSource.getRepository(Student).find({
        where: { phone: In([...phones]) },
      })),
    );
  }
  const studentsByNo = new Map<string, Student>();
  const studentsByPhone = new Map<string, Student>();
  for (const student of students) {
    if (student.studentNo) studentsByNo.set(student.studentNo, student);
    if (student.phone) studentsByPhone.set(student.phone, student);
  }

  const rooms =
    roomNumbers.size > 0
      ? await dataSource.getRepository(Room).find({
          where: { roomNumber: In([...roomNumbers]) },
        })
      : [];
  const roomsByNumber = new Map<string, Room>();
  for (const room of rooms) roomsByNumber.set(room.roomNumber, room);

  const organizations =
    organizationNames.size > 0 ? await dataSource.getRepository(Organization).find() : [];
  const organizationsByName = new Map<string, Organization>();
  for (const org of organizations) organizationsByName.set(org.name, org);

  const activeOccupancies = new Map<number, Occupancy[]>();
  if (stepKey === 'checkins' || stepKey === 'transfers') {
    const studentIds = [...new Set(students.map((s) => s.id))];
    if (studentIds.length > 0) {
      const occupancies = await dataSource.getRepository(Occupancy).find({
        where: { studentId: In(studentIds), status: 'active' },
      });
      for (const occupancy of occupancies) {
        const list = activeOccupancies.get(occupancy.studentId) ?? [];
        list.push(occupancy);
        activeOccupancies.set(occupancy.studentId, list);
      }
    }
  }

  return {
    studentsByNo,
    studentsByPhone,
    roomsByNumber,
    activeOccupancies,
    organizations: organizationsByName,
  };
}
