import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Occupancy } from '../entities/occupancy.entity';
import { Bill } from '../entities/bill.entity';
import { RoomExpense } from '../entities/room-expense.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';

export function nextMonth(ym: string): string {
  const d = new Date(`${ym}-01`);
  d.setMonth(d.getMonth() + 1);
  return d.toISOString().slice(0, 7) + '-01';
}

export function applyClassScope(
  qb: { andWhere: (condition: string, parameters?: Record<string, unknown>) => unknown },
  alias: string,
  accessibleClassIds?: number[],
) {
  if (accessibleClassIds) {
    if (accessibleClassIds.length === 0) {
      qb.andWhere('1 = 0');
      return;
    }
    qb.andWhere(`${alias}.classId IN (:...accessibleClassIds)`, { accessibleClassIds });
  }
}

@Injectable()
export class DashboardQueriesService {
  constructor(
    @InjectRepository(AttendanceRecord) private readonly attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(Bill) private readonly billRepo: Repository<Bill>,
    @InjectRepository(Occupancy) private readonly occRepo: Repository<Occupancy>,
    @InjectRepository(RoomExpense) private readonly expRepo: Repository<RoomExpense>,
  ) {}

async getAttendanceTrend(
    attendanceRepo: Repository<AttendanceRecord>,
    todayStr: string,
    accessibleClassIds?: number[],
  ) {
  const thirtyDaysAgo = new Date(todayStr);
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 29);
  const startStr = thirtyDaysAgo.toISOString().slice(0, 10);

  const trendQb = attendanceRepo
    .createQueryBuilder('a')
    .select('a.attendanceDate', 'date')
    .addSelect('a.status', 'status')
    .addSelect('COUNT(*)', 'count')
    .where('a.attendanceDate >= :start', { start: startStr })
    .andWhere('a.attendanceDate <= :today', { today: todayStr });
  applyClassScope(trendQb, 'a', accessibleClassIds);

  const rows = await trendQb
    .groupBy('a.attendanceDate')
    .addGroupBy('a.status')
    .orderBy('a.attendanceDate', 'ASC')
    .getRawMany();

  const dayMap = new Map<string, { total: number; present: number }>();
  for (const row of rows) {
    const d = dayMap.get(row.date) || { total: 0, present: 0 };
    const cnt = parseInt(row.count, 10);
    d.total += cnt;
    if (row.status === 'present') d.present += cnt;
    dayMap.set(row.date, d);
  }

  return Array.from(dayMap.entries()).map(([date, d]) => ({
    date,
    rate: d.total > 0 ? ((d.present / d.total) * 100).toFixed(1) : 0,
  }));
}


async getIncomeTrend(
    billRepo: Repository<Bill>,
    currentMonth: string,
  ) {
  const results: { month: string; amount: number }[] = [];

  for (let i = 5; i >= 0; i--) {
    const d = new Date(`${currentMonth}-01`);
    d.setMonth(d.getMonth() - i);
    const m = d.toISOString().slice(0, 7);

    const row = await billRepo
      .createQueryBuilder('b')
      .select('SUM(b.totalAmount)', 'total')
      .where('b.status = :paid', { paid: 'paid' })
      .andWhere('b.periodStart >= :start', { start: `${m}-01` })
      .andWhere('b.periodStart < :end', { end: nextMonth(m) })
      .getRawOne();

    results.push({
      month: m,
      amount: parseFloat(row?.total || '0'),
    });
  }

  return results;
}


// 甘特图数据：每个宿舍的入住时间线

async getGanttData(
    occRepo: Repository<Occupancy>,
    assertPeriodRange: (start?: string, end?: string) => void,
    query?: { periodStart?: string; periodEnd?: string; building?: string },
  ) {
  assertPeriodRange(query?.periodStart, query?.periodEnd);
  const qb = occRepo
    .createQueryBuilder('o')
    .leftJoinAndSelect('o.student', 'student')
    .leftJoinAndSelect('o.room', 'room')
    .where('room.status != :archived', { archived: 'archived' })
    .andWhere('o.status = :status', { status: 'active' })
    .orderBy('room.roomNumber', 'ASC')
    .addOrderBy('o.checkInDate', 'ASC');

  if (query?.building) {
    qb.andWhere('room.building = :building', { building: query.building });
  }
  if (query?.periodStart) {
    qb.andWhere('(o.checkOutDate IS NULL OR o.checkOutDate >= :ps)', { ps: query.periodStart });
  }
  if (query?.periodEnd) {
    qb.andWhere('o.checkInDate <= :pe', { pe: query.periodEnd });
  }

  const records = await qb.getMany();

  // 按宿舍分组
  const roomMap = new Map<string, Record<string, unknown>[]>();
  for (const r of records) {
    const key = r.room?.roomNumber || String(r.roomId);
    if (!roomMap.has(key)) roomMap.set(key, []);
    roomMap.get(key)!.push({
      studentName: r.student?.name || '未知',
      studentId: r.studentId,
      checkInDate: r.checkInDate,
      checkOutDate: r.checkOutDate,
      billingStartDate: r.billingStartDate,
      billingEndDate: r.billingEndDate,
    });
  }

  return Array.from(roomMap.entries()).map(([roomNumber, occupancies]) => ({
    roomNumber,
    occupancies,
  }));
}
// 费用统计

async getExpenseStats(
    expRepo: Repository<RoomExpense>,
    assertPeriodRange: (start?: string, end?: string) => void,
    periodStart?: string,
    periodEnd?: string,
  ) {
  assertPeriodRange(periodStart, periodEnd);
  const qb = expRepo
    .createQueryBuilder('e')
    .select('e.expenseType', 'type')
    .addSelect('SUM(e.amount)', 'total')
    .groupBy('e.expenseType');
  if (periodStart) qb.andWhere('e.periodStart >= :ps', { ps: periodStart });
  if (periodEnd) qb.andWhere('e.periodEnd <= :pe', { pe: periodEnd });
  return qb.getRawMany();
}

// 各宿舍费用排行

async getRoomExpenseRanking(
    expRepo: Repository<RoomExpense>,
    assertPeriodRange: (start?: string, end?: string) => void,
    periodStart?: string,
    periodEnd?: string,
  ) {
  assertPeriodRange(periodStart, periodEnd);
  const qb = expRepo
    .createQueryBuilder('e')
    .leftJoin('e.room', 'room')
    .select('room.roomNumber', 'roomNumber')
    .addSelect('SUM(e.amount)', 'total')
    .where('room.status != :archived', { archived: 'archived' })
    .groupBy('e.roomId')
    .orderBy('total', 'DESC')
    .limit(20);
  if (periodStart) qb.andWhere('e.periodStart >= :ps', { ps: periodStart });
  if (periodEnd) qb.andWhere('e.periodEnd <= :pe', { pe: periodEnd });
  return qb.getRawMany();
}

// 班级考勤排行

async getClassAttendanceRanking(
    attendanceRepo: Repository<AttendanceRecord>,
    applyClassScope: (
      qb: { andWhere: (condition: string, parameters?: Record<string, unknown>) => unknown },
      alias: string,
      accessibleClassIds?: number[],
    ) => void,
    accessibleClassIds?: number[],
  ) {
  if (accessibleClassIds?.length === 0) return { top: [], bottom: [] };
  const qb = attendanceRepo
    .createQueryBuilder('a')
    .leftJoin('a.class', 'class')
    .select('class.id', 'classId')
    .addSelect('class.name', 'className')
    .addSelect('a.status', 'status')
    .addSelect('COUNT(*)', 'count');
  applyClassScope(qb, 'a', accessibleClassIds);
  qb.groupBy('class.id').addGroupBy('class.name').addGroupBy('a.status');
  const raw = await qb.getRawMany();

  const classMap = new Map<number, { className: string; present: number; total: number }>();
  for (const r of raw) {
    if (!r.classId) continue;
    if (!classMap.has(Number(r.classId)))
      classMap.set(Number(r.classId), { className: r.className, present: 0, total: 0 });
    const entry = classMap.get(Number(r.classId))!;
    const n = parseInt(r.count, 10);
    entry.total += n;
    if (r.status === 'present') entry.present += n;
  }

  const ranked = Array.from(classMap.values())
    .map((e) => ({
      ...e,
      rate: e.total > 0 ? parseFloat(((e.present / e.total) * 100).toFixed(1)) : 0,
    }))
    .sort((a, b) => b.rate - a.rate);

  return { top: ranked.slice(0, 5), bottom: ranked.slice(-5).reverse() };
}

}
