import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { AttendanceDevice, AttendanceDeviceStatus, Classroom } from '../entities';
import { CreateAttendanceDeviceDto, UpdateAttendanceDeviceDto } from './dto/attendance-device.dto';

@Injectable()
export class AttendanceDevicesService {
  constructor(
    @InjectRepository(AttendanceDevice)
    private readonly repo: Repository<AttendanceDevice>,
    @InjectRepository(Classroom)
    private readonly classroomRepo: Repository<Classroom>,
  ) {}

  private normalizeSn(sn: string): string {
    return sn.trim();
  }

  private async assertClassroomExists(classroomId: number): Promise<void> {
    const exists = await this.classroomRepo.exist({ where: { id: classroomId } });
    if (!exists) throw new BadRequestException('绑定教室不存在');
  }

  async findAll(query?: { classroomId?: number; status?: AttendanceDeviceStatus | 'active' | 'disabled' }) {
    const where: Record<string, unknown> = {};
    if (query?.classroomId) where.classroomId = query.classroomId;
    if (query?.status) where.status = query.status;
    return this.repo.find({
      where,
      relations: ['classroom'],
      order: { classroomId: 'ASC', deviceName: 'ASC' },
    });
  }

  async findOne(id: number) {
    const device = await this.repo.findOne({ where: { id }, relations: ['classroom'] });
    if (!device) throw new NotFoundException('考勤机不存在');
    return device;
  }

  async create(dto: CreateAttendanceDeviceDto) {
    const deviceSn = this.normalizeSn(dto.deviceSn);
    await this.assertClassroomExists(dto.classroomId);
    const exists = await this.repo.findOne({ where: { deviceSn } });
    if (exists) throw new BadRequestException(`SN ${deviceSn} 已绑定教室`);
    const saved = await this.repo.save(
      this.repo.create({
        ...dto,
        deviceSn,
        deviceName: dto.deviceName.trim(),
        status: dto.status ?? AttendanceDeviceStatus.ACTIVE,
      }),
    );
    return this.findOne(saved.id);
  }

  async update(id: number, dto: UpdateAttendanceDeviceDto) {
    const device = await this.repo.findOne({ where: { id } });
    if (!device) throw new NotFoundException('考勤机不存在');
    const patch: Partial<AttendanceDevice> = { ...dto };
    if (dto.classroomId != null) await this.assertClassroomExists(dto.classroomId);
    if (dto.deviceSn != null) {
      const deviceSn = this.normalizeSn(dto.deviceSn);
      const exists = await this.repo.findOne({ where: { deviceSn } });
      if (exists && exists.id !== id) throw new BadRequestException(`SN ${deviceSn} 已绑定教室`);
      patch.deviceSn = deviceSn;
    }
    if (dto.deviceName != null) patch.deviceName = dto.deviceName.trim();
    await this.repo.update(id, patch);
    return this.findOne(id);
  }

  async remove(id: number) {
    const device = await this.repo.findOne({ where: { id } });
    if (!device) throw new NotFoundException('考勤机不存在');
    if (device.status === AttendanceDeviceStatus.DISABLED) {
      throw new BadRequestException('考勤机已停用');
    }
    await this.repo.update(id, { status: AttendanceDeviceStatus.DISABLED });
    return { message: '已停用（绑定数据已保留）' };
  }

  async findActiveBySn(deviceSns: string[]) {
    const sns = [...new Set(deviceSns.map((sn) => this.normalizeSn(sn)).filter(Boolean))];
    if (sns.length === 0) return new Map<string, AttendanceDevice>();
    const devices = await this.repo.find({
      where: { deviceSn: In(sns), status: AttendanceDeviceStatus.ACTIVE },
      relations: ['classroom'],
    });
    return new Map(devices.map((device) => [device.deviceSn, device]));
  }

  async findActiveByClassroomIds(classroomIds: number[]) {
    const ids = [...new Set(classroomIds.filter((id) => Number.isFinite(id)))];
    if (ids.length === 0) return new Map<number, AttendanceDevice>();
    const devices = await this.repo.find({
      where: { classroomId: In(ids), status: AttendanceDeviceStatus.ACTIVE },
      relations: ['classroom'],
      order: { id: 'ASC' },
    });
    const result = new Map<number, AttendanceDevice>();
    for (const device of devices) {
      if (!result.has(device.classroomId)) result.set(device.classroomId, device);
    }
    return result;
  }
}
