import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Deposit } from '../entities/deposit.entity';
import { Student } from '../entities/student.entity';
import { DepositInstallment } from '../entities/deposit-installment.entity';
import { Occupancy } from '../entities/occupancy.entity';

import { BatchCreateDepositDto, CreateDepositDto, RefundDepositDto } from './dto/deposit.dto';

const money = (value: number | string | null | undefined) => Number(Number(value || 0).toFixed(2));

const capacityRoomTypeText: Record<number, string> = {
  1: '单人间',
  2: '二人间',
  3: '三人间',
  4: '四人间',
  5: '五人间',
  6: '六人间',
  8: '八人间',
};

const normalizeRoomType = (roomType?: string | null, capacity?: number | string | null) => {
  const trimmed = roomType?.trim();
  if (trimmed) return trimmed;
  const normalizedCapacity = Number(capacity);
  return capacityRoomTypeText[normalizedCapacity] || (normalizedCapacity > 0 ? `${normalizedCapacity}人间` : '');
};

const roomTypeCapacity = (roomType?: string) => {
  const text = roomType?.trim();
  if (!text) return undefined;
  const knownCapacity = Object.entries(capacityRoomTypeText).find(([, label]) => label === text);
  if (knownCapacity) return Number(knownCapacity[0]);
  const match = text.match(/^(\d+)人间$/);
  return match ? Number(match[1]) : undefined;
};

@Injectable()
export class DepositsService {

  constructor(
    @InjectRepository(Deposit) private repo: Repository<Deposit>,
    @InjectRepository(DepositInstallment)
    private installmentRepo: Repository<DepositInstallment>,
    @InjectRepository(Student)
    private studentRepo: Repository<Student>,
    @InjectRepository(Occupancy)
    private occupancyRepo?: Repository<Occupancy>,
  ) {}

  async getStudentLookups() {
    return this.studentRepo.find({
      select: ['id', 'name', 'studentNo'],
      where: { status: 'active' },
      order: { name: 'ASC' },
    });
  }

  async getEligibleStudents(roomType?: string) {
    const trimmedRoomType = roomType?.trim();
    const fallbackCapacity = roomTypeCapacity(trimmedRoomType);
    const qb = this.occupancyRepo!
      .createQueryBuilder('o')
      .innerJoin('o.student', 'student')
      .innerJoin('o.room', 'room')
      .leftJoin(Deposit, 'deposit', 'deposit.student_id = student.id AND deposit.status != :archived', {
        archived: 'archived',
      })
      .select('student.id', 'studentId')
      .addSelect('student.name', 'studentName')
      .addSelect('student.studentNo', 'studentNo')
      .addSelect('room.id', 'roomId')
      .addSelect('room.roomNumber', 'roomNumber')
      .addSelect('room.building', 'building')
      .addSelect('room.roomType', 'roomType')
      .addSelect('room.capacity', 'capacity')
      .addSelect('deposit.amount', 'depositAmount')
      .where('o.status = :activeStatus', { activeStatus: 'active' })
      .andWhere('o.checkOutDate IS NULL')
      .andWhere('student.status = :studentStatus', { studentStatus: 'active' })
      .orderBy('room.building', 'ASC')
      .addOrderBy('room.roomNumber', 'ASC')
      .addOrderBy('student.name', 'ASC');

    if (trimmedRoomType) {
      if (fallbackCapacity) {
        qb.andWhere(
          '(room.roomType = :roomType OR ((room.roomType IS NULL OR room.roomType = :emptyRoomType) AND room.capacity = :fallbackCapacity))',
          { roomType: trimmedRoomType, emptyRoomType: '', fallbackCapacity },
        );
      } else {
        qb.andWhere('room.roomType = :roomType', { roomType: trimmedRoomType });
      }
    }

    const rows = await qb.getRawMany();
    return rows.map((row) => ({
      studentId: Number(row.studentId),
      studentName: row.studentName,
      studentNo: row.studentNo ?? null,
      roomId: Number(row.roomId),
      roomNumber: row.roomNumber,
      building: row.building ?? null,
      roomType: normalizeRoomType(row.roomType, row.capacity),
      capacity: Number(row.capacity),
      depositAmount: money(row.depositAmount),
    }));
  }

  async batchCreate(dto: BatchCreateDepositDto, userId?: number) {
    const studentIds = [...new Set(dto.studentIds)];
    if (studentIds.length === 0) throw new BadRequestException('请选择学生');
    const amount = money(dto.amount);
    if (!Number.isFinite(dto.amount) || Math.abs(dto.amount * 100 - Math.round(dto.amount * 100)) > 1e-8) {
      throw new BadRequestException('收取金额最多保留两位小数');
    }
    if (amount <= 0) throw new BadRequestException('收取金额必须大于0');

    const results: Deposit[] = [];
    for (const studentId of studentIds) {
      results.push(await this.create({
        studentId,
        amount,
        paidDate: dto.paidDate,
        notes: dto.notes,
      }, userId));
    }
    return { count: results.length, amount, results };
  }

  async findAll(query?: { studentId?: number; status?: string }) {
    const qb = this.repo
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.student', 'student')
      .leftJoinAndSelect('d.installments', 'installments')
      .orderBy('d.createdAt', 'DESC');
    if (query?.studentId) qb.andWhere('d.studentId = :studentId', { studentId: query.studentId });
    if (query?.status) qb.andWhere('d.status = :status', { status: query.status });
    else qb.andWhere('d.status != :archived', { archived: 'archived' });
    const deposits = await qb.getMany();
    for (const deposit of deposits) {
      deposit.installments = deposit.installments?.filter((item) => item.status !== 'archived') ?? [];
    }
    return deposits;
  }

  async findOne(id: number) {
    const deposit = await this.repo.findOne({ where: { id }, relations: ['student', 'installments'] });
    if (!deposit) throw new NotFoundException('押金记录不存在');
    deposit.installments = deposit.installments?.filter((item) => item.status !== 'archived') ?? [];
    return deposit;
  }

  async create(dto: CreateDepositDto, userId?: number) {
    const student = await this.studentRepo.findOne({ where: { id: dto.studentId } });
    if (!student) throw new NotFoundException('学生不存在');
    const amount = money(dto.amount);
    if (!Number.isFinite(dto.amount) || Math.abs(dto.amount * 100 - Math.round(dto.amount * 100)) > 1e-8) {
      throw new BadRequestException('收取金额最多保留两位小数');
    }
    if (amount <= 0) throw new BadRequestException('收取金额必须大于0');

    const existing = await this.repo.findOne({ where: { studentId: dto.studentId } });
    if (existing) {
      existing.amount = money(Number(existing.amount || 0) + amount);
      existing.paidDate = dto.paidDate;
      existing.status = 'paid';
      existing.recordedBy = userId ?? null;
      existing.refundDate = null as unknown as string;
      existing.refundAmount = null as unknown as number;
      existing.refundedBy = null;
      existing.refundedAt = null;
      if (dto.notes) existing.notes = dto.notes;
      return this.repo.save(existing);
    }

    return this.repo.save(
      this.repo.create({
        studentId: dto.studentId,
        amount,
        paidDate: dto.paidDate,
        notes: dto.notes,
        status: 'paid',
        recordedBy: userId,
      }),
    );
  }

  async addInstallment(depositId: number, amount: number, dueDate: string) {
    const normalizedAmount = money(amount);
    if (!Number.isFinite(amount) || Math.abs(amount * 100 - Math.round(amount * 100)) > 1e-8) {
      throw new BadRequestException('分期金额最多保留两位小数');
    }
    if (normalizedAmount <= 0) throw new BadRequestException('分期金额必须大于0');
    const deposit = await this.repo.findOne({ where: { id: depositId } });
    if (!deposit) throw new NotFoundException('押金记录不存在');

    const installment = this.installmentRepo.create({
      depositId,
      amount: normalizedAmount,
      dueDate,
      status: 'pending',
    });
    return this.installmentRepo.save(installment);
  }

  async updateInstallment(id: number, data: { paidDate?: string; status?: string }) {
    const installment = await this.installmentRepo.findOne({ where: { id } });
    if (!installment) throw new NotFoundException('分期记录不存在');

    if (data.paidDate !== undefined) installment.paidDate = data.paidDate;
    if (data.status !== undefined) installment.status = data.status;
    return this.installmentRepo.save(installment);
  }

  async deleteInstallment(id: number) {
    const installment = await this.installmentRepo.findOne({ where: { id } });
    if (!installment) throw new NotFoundException('分期记录不存在');
    if (installment.status === 'archived') throw new BadRequestException('分期记录已归档');
    await this.installmentRepo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  async refund(id: number, dto: RefundDepositDto, userId?: number) {
    const deposit = await this.repo.findOne({ where: { id } });
    if (!deposit) throw new NotFoundException('押金记录不存在');
    if (deposit.status !== 'paid' || Number(deposit.amount) <= 0) {
      throw new BadRequestException('该学生当前没有可退押金');
    }

    const refundAmount = money(deposit.amount);

    deposit.refundDate = dto.refundDate;
    deposit.refundAmount = refundAmount;
    deposit.amount = 0;
    deposit.status = 'refunded';
    if (dto.notes) deposit.notes = dto.notes;
    deposit.refundedBy = userId ?? null;
    deposit.refundedAt = new Date();

    return this.repo.save(deposit);
  }

  async remove(id: number) {
    const deposit = await this.repo.findOne({ where: { id } });
    if (!deposit) throw new NotFoundException('押金记录不存在');
    if (deposit.status === 'archived') throw new BadRequestException('押金记录已归档');
    await this.repo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  async getStats() {
    const qb = this.repo
      .createQueryBuilder('d')
      .select('d.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .addSelect('SUM(d.amount)', 'totalAmount');
    qb.groupBy('d.status');
    return qb.getRawMany();
  }
}
