import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Query,
  Req,
  Res,
  HttpCode,
  HttpStatus,
  ParseIntPipe,
  UseGuards,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { Response } from 'express';
import {
  ApiTags,
  ApiOperation,
  ApiBearerAuth,
  ApiSecurity,
  ApiParam,
} from '@nestjs/swagger';
import { ResponseService } from 'src/common/response-handling/response-handler';
import { handleControllerError } from 'src/common/utils/controller-response-handling';
import { RolesGuard } from 'src/common/guards/roles.guard';
import { Roles } from 'src/common/decorators/roles.decorator';
import { CombinedAuthGuard } from 'src/auth/combined-auth.guard';
import { AuthenticatedRequest } from 'src/common/interfaces/authenticated-request.interface';
import { OnlineAttendanceService } from './online-attendance.service';
import { AttendanceReportQueryDto } from './dto/attendance-report-query.dto';
import { JoinSessionDto } from './dto/join-session.dto';
import { MarkAttendanceDto, SetAttendanceDto } from './dto/mark-attendance.dto';
import { ROLE_VALUES } from 'src/common/constants/strings-constants';
import {
  ONLINE_ATTENDANCE_RESPONSE,
  ONLINE_ATTENDANCE_SWAGGER,
} from 'src/common/constants/online-attendance.constants';

/** Roles allowed to manually mark/undo attendance (coordinator, RM, admin). */
const ATTENDANCE_MARKER_ROLES = [ROLE_VALUES.ADMIN, ROLE_VALUES.COORDINATOR, ROLE_VALUES.RELATIONAL_MANAGER];

@ApiTags(ONLINE_ATTENDANCE_SWAGGER.TAG)
@Controller('online-attendance')
@UseGuards(CombinedAuthGuard, RolesGuard)
@ApiBearerAuth('Authorization')
@ApiSecurity('userIdAuth')
@ApiSecurity('activeRoleAuth')
export class OnlineAttendanceController {
  constructor(
    private readonly service: OnlineAttendanceService,
    private readonly responseService: ResponseService,
  ) {}

  @Post('sessions/:sessionId/join')
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.JOIN })
  @ApiParam({ name: 'sessionId', type: Number })
  async join(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Body() dto: JoinSessionDto,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const data = await this.service.joinSession(sessionId, req.user?.id, dto);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.JOIN_RESOLVED, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('sessions/:sessionId/attendance/mark')
  @HttpCode(HttpStatus.OK)
  @Roles(...ATTENDANCE_MARKER_ROLES)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.MARK_ATTENDANCE })
  @ApiParam({ name: 'sessionId', type: Number })
  async markAttendance(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Body() dto: SetAttendanceDto,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const roles = req.user?.roles as string[] | undefined;
      const data = await this.service.markAttendance(
        sessionId,
        dto.registrationId,
        dto.status,
        roles,
        req.user?.id,
      );
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.ATTENDANCE_MARKED, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('sessions/:sessionId/attendance/undo')
  @HttpCode(HttpStatus.OK)
  @Roles(...ATTENDANCE_MARKER_ROLES)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.UNDO_ATTENDANCE })
  @ApiParam({ name: 'sessionId', type: Number })
  async undoAttendance(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Body() dto: MarkAttendanceDto,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const roles = req.user?.roles as string[] | undefined;
      const data = await this.service.undoAttendance(
        sessionId,
        dto.registrationId,
        roles,
        req.user?.id,
      );
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.ATTENDANCE_UNDONE, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('sessions/:sessionId/attendance/unmark')
  @HttpCode(HttpStatus.OK)
  @Roles('admin', ROLE_VALUES.COORDINATOR, ROLE_VALUES.RELATIONAL_MANAGER)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.UNMARK_ATTENDANCE })
  @ApiParam({ name: 'sessionId', type: Number })
  async unmarkAttendance(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Body() dto: MarkAttendanceDto,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const roles = req.user?.roles as string[] | undefined;
      const data = await this.service.unmarkAttendance(sessionId, dto.registrationId, roles, req.user?.id);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.ATTENDANCE_UNMARKED, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('sessions/:sessionId/attendance/lock')
  @HttpCode(HttpStatus.OK)
  @Roles(ROLE_VALUES.COORDINATOR)
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.LOCK_ATTENDANCE })
  @ApiParam({ name: 'sessionId', type: Number })
  async lockAttendance(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const data = await this.service.lockSessionAttendance(sessionId, req.user?.id);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.ATTENDANCE_LOCKED, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('sessions/:sessionId/attendance/unlock')
  @HttpCode(HttpStatus.OK)
  @Roles(ROLE_VALUES.COORDINATOR)
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.UNLOCK_ATTENDANCE })
  @ApiParam({ name: 'sessionId', type: Number })
  async unlockAttendance(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Req() req: AuthenticatedRequest,
    @Res() res: Response,
  ) {
    try {
      const data = await this.service.unlockSessionAttendance(sessionId, req.user?.id);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.ATTENDANCE_UNLOCKED, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Get('sessions/:sessionId/report')
  @HttpCode(HttpStatus.OK)
  @Roles('admin', ROLE_VALUES.COORDINATOR, ROLE_VALUES.RELATIONAL_MANAGER)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.SESSION_REPORT })
  @ApiParam({ name: 'sessionId', type: Number })
  async sessionReport(
    @Param('sessionId', ParseIntPipe) sessionId: number,
    @Query() query: AttendanceReportQueryDto,
    @Res() res: Response,
  ) {
    try {
      if (query.download) {
        const data = await this.service.exportSessionReport(sessionId);
        await this.responseService.success(
          res,
          ONLINE_ATTENDANCE_RESPONSE.SESSION_REPORT_EXPORT,
          data,
        );
        return;
      }
      const data = await this.service.sessionReport(sessionId, query);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.SESSION_REPORT, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Get('programs/:programId/report')
  @HttpCode(HttpStatus.OK)
  @Roles('admin', ROLE_VALUES.COORDINATOR, ROLE_VALUES.RELATIONAL_MANAGER)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({ summary: ONLINE_ATTENDANCE_SWAGGER.PROGRAM_REPORT })
  @ApiParam({ name: 'programId', type: Number })
  async programReport(
    @Param('programId', ParseIntPipe) programId: number,
    @Query() query: AttendanceReportQueryDto,
    @Res() res: Response,
  ) {
    try {
      if (query.download) {
        const data = await this.service.exportProgramReport(programId, query);
        await this.responseService.success(
          res,
          ONLINE_ATTENDANCE_RESPONSE.PROGRAM_REPORT_EXPORT,
          data,
        );
        return;
      }
      const data = await this.service.programReport(programId, query);
      await this.responseService.success(res, ONLINE_ATTENDANCE_RESPONSE.PROGRAM_REPORT, data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }
}
