import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
  Request,
  ForbiddenException,
  ConflictException,
} from '@nestjs/common';
import { AuthorizationService, CaslAction, SubjectName } from '../authorization';
import { SchedulesService } from './schedules.service';
import {
  CreateScheduleDto,
  UpdateScheduleDto,
  QueryScheduleDto,
  WeeklyViewQueryDto,
} from './dto/schedule.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { extractRequestInfo } from '../common/request-utils';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationType } from '../entities/notification.entity';
import { RequirePermission } from '../auth/decorators/permission.decorator';

interface RequestUser {
  id: number;
  username: string;
  permissions: string[];
  isSuperAdmin: boolean;
}

@UseGuards(JwtAuthGuard)
@Controller('class-schedules')
export class SchedulesController {
  constructor(
    private readonly service: SchedulesService,
    private readonly logService: OperationLogsService,
    private readonly notificationsService: NotificationsService,
    private readonly authService: AuthorizationService,
  ) {}

  private canManageAllSchedules(req: { user: RequestUser }): boolean {
    const ability = this.authService.abilityForRequest(req);
    // Legacy: class:edit grants broad schedule access for teachers
    return (
      ability.can(CaslAction.Manage, SubjectName.Schedule) ||
      ability.can(CaslAction.Update, SubjectName.Class)
    );
  }

  private assertClassAccess(req: { user: RequestUser }, classId: number) {
    return this.service.assertClassAccess(req.user.id, classId, this.canManageAllSchedules(req));
  }

  private async getAuthorizedSchedule(id: number, req: { user: RequestUser }) {
    const schedule = await this.service.findOne(id);
    if (schedule.classId == null) {
      if (!this.canManageAllSchedules(req)) {
        throw new ForbiddenException('无权访问该排课详情');
      }
      return schedule;
    }
    await this.assertClassAccess(req, schedule.classId);
    return schedule;
  }

  @Get('lookups')
  @RequirePermission('schedule:view')
  async getLookups(@Request() req: { user: RequestUser }) {
    const classIds = await this.service.getAccessibleClassIds(
      req.user.id,
      this.canManageAllSchedules(req),
    );
    return this.service.getLookups(classIds);
  }

  @Get()
  @RequirePermission('schedule:view')
  async findAll(@Query() query: QueryScheduleDto, @Request() req: { user: RequestUser }) {
    const classIds = await this.service.getAccessibleClassIds(
      req.user.id,
      this.canManageAllSchedules(req),
    );
    return this.service.findAll(query, classIds);
  }

  @Get('weekly')
  @RequirePermission('schedule:view')
  async getWeeklyView(@Query() query: WeeklyViewQueryDto, @Request() req: { user: RequestUser }) {
    const classIds = await this.service.getAccessibleClassIds(
      req.user.id,
      this.canManageAllSchedules(req),
    );
    return this.service.getWeeklyView(query, classIds);
  }

  @Get('classes/:classId/teachers')
  @RequirePermission('schedule:view')
  async getClassTeachers(@Param('classId') classId: string, @Request() req: { user: RequestUser }) {
    const classIds = await this.service.getAccessibleClassIds(
      req.user.id,
      this.canManageAllSchedules(req),
    );
    if (classIds && !classIds.includes(+classId)) return [];
    return this.service.getClassTeachers(+classId);
  }

  @Get('classroom/:id/occupancy')
  @RequirePermission('schedule:view')
  async getClassroomOccupancy(
    @Param('id') id: string,
    @Query('date') date: string | undefined,
    @Request() req: { user: RequestUser },
  ) {
    const schedules = await this.service.getClassroomOccupancy(+id, date);
    const classIds = await this.service.getAccessibleClassIds(
      req.user.id,
      this.canManageAllSchedules(req),
    );
    if (!classIds) return schedules.map((schedule) => ({ ...schedule, canViewDetails: true }));
    const allowed = new Set(classIds);
    return schedules.map((schedule) =>
      schedule.classId !== null && allowed.has(schedule.classId)
        ? { ...schedule, canViewDetails: true }
        : this.service.maskScheduleOccupancy(schedule),
    );
  }

  @Get(':id')
  @RequirePermission('schedule:view')
  async findOne(@Param('id') id: string, @Request() req: { user: RequestUser }) {
    return this.getAuthorizedSchedule(+id, req);
  }

  @Post()
  @RequirePermission('schedule:create')
  async create(
    @Body() dto: CreateScheduleDto,
    @Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
  ) {
    const { ipAddress, userAgent } = extractRequestInfo(req);
    await this.assertClassAccess(req as { user: RequestUser }, dto.classId);
    try {
      const result = await this.service.create(dto);
      await this.logService.log({
        userId: req.user?.id,
        username: req.user?.username,
        module: '排课管理',
        action: '创建排课',
        targetId: result.id,
        targetType: 'class-schedule',
        detail: `${result.subject} 周${result.weekDay} ${result.startTime}-${result.endTime}`,
        ipAddress,
        userAgent,
      });
      return result;
    } catch (error) {
      if (error instanceof ConflictException) {
        try {
          const conflicts = await this.service.checkConflict(
            dto.classroomId,
            dto.weekDay,
            dto.startTime,
            dto.endTime,
            dto.startDate,
            dto.endDate,
          );
          const teacherIds = [
            ...new Set(conflicts.map((c) => c.teacherId).filter((id): id is number => id != null)),
          ];
          if (teacherIds.length > 0) {
            void this.notificationsService.create({
              recipientIds: teacherIds,
              type: NotificationType.SCHEDULE_CONFLICT,
              title: '排课冲突',
              content: `教室${dto.classroomId} 周${dto.weekDay} ${dto.startTime}-${dto.endTime} 与已有排课冲突`,
            });
          }
        } catch {
          // Best-effort conflict notification must not hide the original conflict.
        }
      }
      throw error;
    }
  }

  @Put(':id')
  @RequirePermission('schedule:edit')
  async update(
    @Param('id') id: string,
    @Body() dto: UpdateScheduleDto,
    @Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
  ) {
    const { ipAddress, userAgent } = extractRequestInfo(req);
    const existing = await this.getAuthorizedSchedule(+id, req as { user: RequestUser });
    if (dto.classId !== undefined && dto.classId !== existing.classId) {
      await this.assertClassAccess(req as { user: RequestUser }, dto.classId);
    }
    try {
      const result = await this.service.update(+id, dto);
      await this.logService.log({
        userId: req.user?.id,
        username: req.user?.username,
        module: '排课管理',
        action: '编辑排课',
        targetId: +id,
        targetType: 'class-schedule',
        detail: JSON.stringify(dto),
        ipAddress,
        userAgent,
      });
      return result;
    } catch (error) {
      if (error instanceof ConflictException) {
        try {
          const conflicts = await this.service.checkConflict(
            existing.classroomId,
            existing.weekDay,
            existing.startTime,
            existing.endTime,
            existing.startDate,
            existing.endDate,
          );
          const teacherIds = [
            ...new Set(conflicts.map((c) => c.teacherId).filter((id): id is number => id != null)),
          ];
          if (teacherIds.length > 0) {
            void this.notificationsService.create({
              recipientIds: teacherIds,
              type: NotificationType.SCHEDULE_CONFLICT,
              title: '排课冲突',
              content: `教室${existing.classroomId} 周${existing.weekDay} ${existing.startTime}-${existing.endTime} (更新) 与已有排课冲突`,
            });
          }
        } catch {
          // Best-effort conflict notification must not hide the original conflict.
        }
      }
      throw error;
    }
  }

  @Delete(':id')
  @RequirePermission('schedule:delete')
  async remove(
    @Param('id') id: string,
    @Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
  ) {
    const { ipAddress, userAgent } = extractRequestInfo(req);
    await this.getAuthorizedSchedule(+id, req as { user: RequestUser });
    const result = await this.service.remove(+id);
    await this.logService.log({
      userId: req.user?.id,
      username: req.user?.username,
      module: '排课管理',
      action: '停用排课',
      targetId: +id,
      targetType: 'class-schedule',
      ipAddress,
      userAgent,
    });
    return result;
  }
}
