import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ConflictException,
} from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { ClassroomRental, ClassroomRentalStatus } from '../entities/classroom-rental.entity';
import { Classroom, ClassroomStatus } from '../entities/classroom.entity';
import { Organization } from '../entities/organization.entity';
import { ClassSchedule } from '../entities/class-schedule.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import { AttendanceSession } from '../entities/attendance-session.entity';
import { CreateRentalDto, UpdateRentalDto } from './dto/rental.dto';
import { RentalScheduleService } from './rental-schedule.service';

import * as path from 'path';
import * as fs from 'fs';
import { randomBytes } from 'crypto';

// 预设色板（与 organizations.service 保持一致，作为颜色兜底）
function rentalConflictError(
  message: string,
  conflicts: Array<{ id: number; startDate: string; endDate: string; lesseeOrganization?: { name?: string | null } | null }>,
) {
  return new ConflictException({
    message,
    conflicts: conflicts.map((c) => ({
      id: c.id,
      startDate: c.startDate,
      endDate: c.endDate,
      organizationName: c.lesseeOrganization?.name,
    })),
  });
}

@Injectable()
export class ClassroomRentalsService {
  constructor(
    @InjectRepository(ClassroomRental) private repo: Repository<ClassroomRental>,
    @InjectRepository(Classroom) private classroomRepo: Repository<Classroom>,
    @InjectRepository(Organization) private organizationRepo: Repository<Organization>,
    @InjectRepository(ClassSchedule) private scheduleRepo: Repository<ClassSchedule>,
    @InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(AttendanceSession) private attendanceSessionRepo: Repository<AttendanceSession>,
    @InjectDataSource() private dataSource: DataSource,
    private schedule: RentalScheduleService,
  ) {}

  get uploadDir(): string {
    const base = process.env.UPLOAD_DIR || './uploads';
    return path.resolve(base, 'contracts');
  }

  ensureUploadDir() {
    if (!fs.existsSync(this.uploadDir)) {
      fs.mkdirSync(this.uploadDir, { recursive: true });
    }
  }

  async findAll(query?: {
    classroomId?: number;
    lesseeOrganizationId?: number;
    month?: string;
    includeEnded?: boolean;
  }) {
    const qb = this.repo
      .createQueryBuilder('r')
      .leftJoinAndSelect('r.classroom', 'classroom')
      .leftJoinAndSelect('r.lesseeOrganization', 'lesseeOrganization')
      .orderBy('r.startDate', 'DESC');
    if (query?.classroomId) qb.andWhere('r.classroomId = :cid', { cid: query.classroomId });
    if (query?.lesseeOrganizationId)
      qb.andWhere('r.lesseeOrganizationId = :oid', { oid: query.lesseeOrganizationId });
    if (query?.month) {
      const [y, m] = query.month.split('-').map(Number);
      const first = `${y}-${String(m).padStart(2, '0')}-01`;
      const lastDay = new Date(y, m, 0).getDate();
      const last = `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
      qb.andWhere('r.startDate <= :last AND r.endDate >= :first', { first, last });
    }
    if (!query?.includeEnded) {
      qb.andWhere('r.status = :active', { active: ClassroomRentalStatus.ACTIVE });
    }
    const rentals = await qb.getMany();
    return rentals.map((rental) => this.schedule.withEffectiveStatus(rental));
  }

  /**
   * Agent tool: 教室租赁订单查询，返回白名单字段。
   */
  async agentSearchRentals(query?: {
    classroomId?: number;
    month?: string;
    includeEnded?: boolean;
    limit?: number;
  }): Promise<
    {
      id: number;
      classroomName: string;
      lesseeOrganizationName: string | null;
      startDate: string;
      endDate: string;
      dailyRate: number | null;
      totalAmount: number | null;
      status: string;
      contractName: string | null;
    }[]
  > {
    const rentalSelects = [
      ['classroom.name', 'classroomName'],
      ['lesseeOrganization.name', 'lesseeOrganizationName'],
      ['r.startDate', 'startDate'],
      ['r.endDate', 'endDate'],
      ['r.dailyRate', 'dailyRate'],
      ['r.totalAmount', 'totalAmount'],
      ['r.status', 'status'],
      ['r.contractOriginalName', 'contractName'],
    ] as const;
    const qb = this.repo
      .createQueryBuilder('r')
      .leftJoin('r.classroom', 'classroom')
      .leftJoin('r.lesseeOrganization', 'lesseeOrganization')
      .select('r.id', 'id');
    for (const [column, alias] of rentalSelects) {
      qb.addSelect(column, alias);
    }
    if (query?.classroomId) {
      qb.andWhere('r.classroomId = :classroomId', { classroomId: query.classroomId });
    }
    if (query?.month) {
      const [y, m] = query.month.split('-').map(Number);
      const first = `${y}-${String(m).padStart(2, '0')}-01`;
      const lastDay = new Date(y, m, 0).getDate();
      const last = `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
      qb.andWhere('r.startDate <= :last AND r.endDate >= :first', { first, last });
    }
    if (!query?.includeEnded) {
      qb.andWhere('r.status = :active', { active: ClassroomRentalStatus.ACTIVE });
    }
    const rows = await qb
      .orderBy('r.startDate', 'DESC')
      .limit(Math.max(1, Math.min(query?.limit ?? 20, 50)))
      .getRawMany<Record<string, unknown>>();
    return rows.map((row) => ({
      id: Number(row.id),
      classroomName: row.classroomName == null ? '' : String(row.classroomName),
      lesseeOrganizationName:
        row.lesseeOrganizationName == null ? null : String(row.lesseeOrganizationName),
      startDate: String(row.startDate),
      endDate: String(row.endDate),
      dailyRate: row.dailyRate == null ? null : Number(row.dailyRate),
      totalAmount: row.totalAmount == null ? null : Number(row.totalAmount),
      status: String(row.status),
      contractName: row.contractName == null ? null : String(row.contractName),
    }));
  }

  async findOne(id: number) {
    const rental = await this.repo.findOne({
      where: { id },
      relations: ['classroom', 'lessorOrganization', 'lesseeOrganization'],
    });
    if (!rental) throw new NotFoundException('租赁订单不存在');
    return this.schedule.withEffectiveStatus(rental);
  }

  async getUnavailableDates(classroomId: number, year: number, month: number, excludeId?: number) {
    return this.schedule.getUnavailableDates(classroomId, year, month, excludeId);
  }

  async findConflicts(classroomId: number, startDate: string, endDate: string, excludeId?: number) {
    return this.schedule.findConflicts(classroomId, startDate, endDate, excludeId);
  }

  async getSchedule(year: number, month: number) {
    return this.schedule.getSchedule(year, month);
  }

  async create(dto: CreateRentalDto, userId?: number) {
    if (dto.startDate > dto.endDate) throw new BadRequestException('起始日期不能晚于结束日期');
    const classroom = await this.classroomRepo.findOne({ where: { id: dto.classroomId } });
    if (!classroom) throw new NotFoundException('教室不存在');
    if (classroom.status !== ClassroomStatus.AVAILABLE) {
      throw new BadRequestException('仅可用教室可以创建租赁');
    }
    const lessorOrganization = dto.lessorOrganizationId
      ? await this.organizationRepo.findOne({
          where: { id: dto.lessorOrganizationId, status: 'active' },
        })
      : await this.organizationRepo.findOne({ where: { isHost: true, status: 'active' } });
    if (!lessorOrganization) throw new NotFoundException('出租机构不存在或未启用');
    const lesseeOrganization = await this.organizationRepo.findOne({
      where: { id: dto.lesseeOrganizationId, status: 'active' },
    });
    if (!lesseeOrganization) throw new NotFoundException('承租机构不存在或未启用');
    if (lessorOrganization.id === lesseeOrganization.id) {
      throw new BadRequestException('出租机构和承租机构不能相同');
    }

    const conflicts = await this.findConflicts(dto.classroomId, dto.startDate, dto.endDate);
    if (conflicts.length > 0) {
      throw rentalConflictError('该教室在此时间段已有租赁', conflicts);
    }
    const rental = this.repo.create({
      ...dto,
      lessorOrganizationId: lessorOrganization.id,
      lesseeOrganizationId: lesseeOrganization.id,
      createdBy: userId,
      status: ClassroomRentalStatus.ACTIVE,
    });
    const saved = await this.repo.save(rental);
    await this.schedule.syncScheduleFromRental(saved, lesseeOrganization.name);
    return saved;
  }

  async update(id: number, dto: UpdateRentalDto) {
    const rental = await this.findOne(id);
    if (rental.effectiveStatus !== ClassroomRentalStatus.ACTIVE) {
      throw new BadRequestException('已结束或已取消的租赁不能编辑');
    }
    // 若修改了教室/日期，重新冲突检查
    const newClassroomId = dto.classroomId ?? rental.classroomId;
    const newStart = dto.startDate ?? rental.startDate;
    const newEnd = dto.endDate ?? rental.endDate;
    if (newStart > newEnd) throw new BadRequestException('起始日期不能晚于结束日期');
    if (dto.classroomId && dto.classroomId !== rental.classroomId) {
      const classroom = await this.classroomRepo.findOne({ where: { id: dto.classroomId } });
      if (!classroom) throw new NotFoundException('教室不存在');
      if (classroom.status !== ClassroomStatus.AVAILABLE) {
        throw new BadRequestException('仅可用教室可以承接租赁');
      }
    }
    if (dto.classroomId || dto.startDate || dto.endDate) {
      const conflicts = await this.findConflicts(newClassroomId, newStart, newEnd, id);
      if (conflicts.length > 0) {
        throw rentalConflictError('修改后时间段与已有租赁冲突', conflicts);
      }
    }
    const newLessorId = dto.lessorOrganizationId ?? rental.lessorOrganizationId;
    const newLesseeId = dto.lesseeOrganizationId ?? rental.lesseeOrganizationId;
    if (newLessorId === newLesseeId) {
      throw new BadRequestException('出租机构和承租机构不能相同');
    }
    if (dto.lessorOrganizationId) {
      const lessor = await this.organizationRepo.findOne({
        where: { id: dto.lessorOrganizationId, status: 'active' },
      });
      if (!lessor) throw new NotFoundException('出租机构不存在或未启用');
    }
    if (dto.lesseeOrganizationId) {
      const lessee = await this.organizationRepo.findOne({
        where: { id: dto.lesseeOrganizationId, status: 'active' },
      });
      if (!lessee) throw new NotFoundException('承租机构不存在或未启用');
    }
    await this.repo.update(id, dto);
    const updated = await this.findOne(id);
    await this.schedule.syncScheduleFromRental(updated);
    return updated;
  }

  async cancel(id: number) {
    const rental = await this.findOne(id);
    if (rental.effectiveStatus !== ClassroomRentalStatus.ACTIVE) {
      throw new BadRequestException('仅有效租赁可以取消');
    }
    await this.repo.update(id, { status: ClassroomRentalStatus.CANCELLED });
    await this.scheduleRepo.update({ rentalId: id, scheduleType: 'RENTAL' }, { status: 'inactive' });
    return this.findOne(id);
  }

  async end(id: number) {
    const rental = await this.findOne(id);
    if (rental.effectiveStatus !== ClassroomRentalStatus.ACTIVE) {
      throw new BadRequestException('仅有效租赁可以结束');
    }
    const today = new Intl.DateTimeFormat('en-CA', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(new Date());
    if (rental.startDate > today) throw new BadRequestException('租赁尚未开始，不能结束');
    await this.repo.update(id, {
      status: ClassroomRentalStatus.ENDED,
      endDate: rental.endDate > today ? today : rental.endDate,
    });
    const ended = await this.findOne(id);
    await this.schedule.syncScheduleFromRental(ended);
    return ended;
  }

  async remove(id: number) {
    const rental = await this.findOne(id);
    if (rental.status === ClassroomRentalStatus.CANCELLED) {
      return { message: '租赁订单已归档' };
    }
    await this.repo.update(id, { status: ClassroomRentalStatus.CANCELLED });
    await this.scheduleRepo.update({ rentalId: id, scheduleType: 'RENTAL' }, { status: 'inactive' });
    return { message: '租赁订单已归档（合同文件已保留）' };
  }

  async purge(id: number) {
    const rental = await this.findOne(id);
    if (rental.status !== ClassroomRentalStatus.CANCELLED) {
      throw new BadRequestException('仅已取消租赁订单可以永久删除，请先取消');
    }
    const schedules = await this.scheduleRepo.find({
      where: { rentalId: id, scheduleType: 'RENTAL' },
    });
    const scheduleIds = schedules.map((schedule) => schedule.id);
    if (scheduleIds.length > 0) {
      const [sessionCount, recordCount] = await Promise.all([
        this.attendanceSessionRepo.count({ where: { scheduleId: In(scheduleIds) } }),
        this.attendanceRepo.count({ where: { scheduleId: In(scheduleIds) } }),
      ]);
      if (sessionCount > 0 || recordCount > 0) {
        throw new BadRequestException('该租赁的排课已有考勤记录，无法永久删除');
      }
    }
    await this.dataSource.transaction(async (manager) => {
      if (scheduleIds.length > 0) {
        await manager.delete(ClassSchedule, { id: In(scheduleIds) });
      }
      await manager.delete(ClassroomRental, id);
    });
    if (rental.contractPath) {
      const fullPath = path.join(this.uploadDir, rental.contractPath);
      try {
        if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
      } catch (error) {
        // 文件删除失败仅告警，不阻塞数据库删除
        console.warn(`[ClassroomRentalsService] 合同文件删除失败: ${fullPath}`, error);
      }
    }
    return { message: '已永久删除租赁订单（不可恢复）' };
  }

  async attachContract(id: number, file: Express.Multer.File) {
    const rental = await this.findOne(id);
    this.ensureUploadDir();
    // 安全校验：MIME + 扩展名
    if (file.mimetype !== 'application/pdf') {
      throw new BadRequestException('仅支持 PDF 文件');
    }
    const ext = path.extname(file.originalname).toLowerCase();
    if (ext !== '.pdf') throw new BadRequestException('文件扩展名必须为 .pdf');
    // UUID 文件名
    const uuid = randomBytes(16).toString('hex');
    const filename = `${uuid}.pdf`;
    const fullPath = path.join(this.uploadDir, filename);
    // 路径遍历防护
    if (!fullPath.startsWith(this.uploadDir)) throw new BadRequestException('路径非法');
    // 替换旧文件
    if (rental.contractPath) {
      const oldPath = path.join(this.uploadDir, rental.contractPath);
      if (fs.existsSync(oldPath)) {
        try {
          fs.unlinkSync(oldPath);
        } catch {
          /* ignore */
        }
      }
    }
    fs.writeFileSync(fullPath, file.buffer);
    await this.repo.update(id, {
      contractPath: filename,
      contractOriginalName: file.originalname,
    });
    return this.findOne(id);
  }

  async removeContract(id: number) {
    const rental = await this.findOne(id);
    if (!rental.contractPath) throw new BadRequestException('该租赁未上传合同');
    const fullPath = path.join(this.uploadDir, rental.contractPath);
    if (fs.existsSync(fullPath)) {
      try {
        fs.unlinkSync(fullPath);
      } catch {
        /* ignore */
      }
    }
    await this.repo.update(id, { contractPath: null, contractOriginalName: null });
    return { message: '合同已移除' };
  }

  /**
   * 获取合同文件的绝对路径（供控制器流式返回），严格校验路径安全
   */
  async getContractPath(id: number): Promise<{ fullPath: string; originalName: string }> {
    const rental = await this.findOne(id);
    if (!rental.contractPath) throw new NotFoundException('该租赁未上传合同');
    const fullPath = path.join(this.uploadDir, rental.contractPath);
    if (!fullPath.startsWith(this.uploadDir)) throw new BadRequestException('路径非法');
    if (!fs.existsSync(fullPath)) throw new NotFoundException('合同文件丢失');
    return { fullPath, originalName: rental.contractOriginalName || 'contract.pdf' };
  }

  /**
   * 获取月度排期矩阵
   */
}
