import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Put,
  Query,
  Request,
  Res,
  UseGuards,
} from '@nestjs/common';
import type { Response, Request as ExpressRequest } from 'express';
import { SkipTransform } from '@common/decorators/skip-transform.decorator';
import { SeriesService } from '../services/series.service';
import { handleMeetingAttendanceError } from '../errors/handle-controller-error';
import { getMeetingRoleFromUser, isMeetingAdminRole } from '../utils/meeting-roles';

@Controller('meeting-attendance/series')
@SkipTransform()
export class MeetingAttendanceSeriesController {
  constructor(private readonly seriesService: SeriesService) {}

  @Get()
  async listSeries(@Res() res: Response) {
    try {
      const result = await this.seriesService.listSeries();
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Post()
  async createSeries(
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const actor = req.user as any;
      const result = await this.seriesService.createSeries(body, req, user.email, actor);
      return res.status(201).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Get(':id')
  async getSeries(@Param('id') id: string, @Res() res: Response) {
    try {
      const series = await this.seriesService.getSeriesDetails(id);
      return res.status(200).json(series);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Put(':id')
  async updateSeries(
    @Param('id') id: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const actor = req.user as any;
      const result = await this.seriesService.updateSeries(id, body, req, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Delete(':id')
  async deleteSeries(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const actor = req.user as any;
      const result = await this.seriesService.deleteSeries(id, req, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Get(':id/history')
  async getSeriesHistory(@Param('id') id: string, @Res() res: Response) {
    try {
      const result = await this.seriesService.getSeriesHistory(id);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Post(':id/update-schedule')
  async updateSchedule(
    @Param('id') id: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const actor = req.user as any;
      const result = await this.seriesService.updateSeriesSchedule(id, body, req, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Get(':id/attendees')
  async getSeriesAttendees(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = await this.requireMeetingUser(req, res);
      if (!actor) {
        return res;
      }

      const result = await this.seriesService.getSeriesAttendees(id, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Post(':id/attendees')
  async setSeriesAttendees(
    @Param('id') id: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = await this.requireMeetingUser(req, res);
      if (!actor) {
        return res;
      }

      const result = await this.seriesService.setSeriesAttendees(id, body, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Patch(':id/attendees')
  async updateAttendeeRole(
    @Param('id') id: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = await this.requireMeetingUser(req, res);
      if (!actor) {
        return res;
      }

      const result = await this.seriesService.updateSeriesAttendeeRole(id, body, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Update failed');
    }
  }

  @Delete(':id/attendees')
  async deleteAttendee(
    @Param('id') id: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = await this.requireMeetingUser(req, res);
      if (!actor) {
        return res;
      }

      const result = await this.seriesService.deleteSeriesAttendee(id, body, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Delete failed');
    }
  }

  @Put(':id/meetings/:meetingId')
  async updateSeriesMeeting(
    @Param('id') id: string,
    @Param('meetingId') meetingId: string,
    @Body() body: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const actor = req.user as any;
      const result = await this.seriesService.updateSeriesMeeting(id, meetingId, body, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Delete(':id/meetings/:meetingId')
  async cancelSeriesMeeting(
    @Param('id') id: string,
    @Param('meetingId') meetingId: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) {
        return res;
      }

      const result = await this.seriesService.cancelSeriesMeeting(id, meetingId);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Patch(':id/enforce-checkin-mode')
  async updateSeriesEnforceCheckinMode(
    @Param('id') id: string,
    @Body() body: { enforceCheckinMode?: boolean },
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) return res;
      if (typeof body?.enforceCheckinMode !== 'boolean') {
        return res.status(400).json({ error: 'enforceCheckinMode must be boolean' });
      }
      const actor = req.user as any;
      const result = await this.seriesService.updateSeriesEnforceCheckinMode(
        id,
        body.enforceCheckinMode,
        req,
        actor,
      );
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Get(':id/attendee-preferences')
  async listSeriesAttendeePreferences(
    @Param('id') id: string,
    @Query() query: Record<string, any>,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) return res;
      const result = await this.seriesService.listSeriesAttendeePreferences(id, query);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Put(':id/attendee-preferences')
  async upsertSeriesAttendeePreferences(
    @Param('id') id: string,
    @Body() body: { preferences?: Array<{ userId: string; defaultCheckinMode: 'ON_SITE' | 'ONLINE' }> },
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) return res;
      if (!Array.isArray(body?.preferences)) {
        return res.status(400).json({ error: 'preferences must be an array' });
      }
      const actor = req.user as any;
      const result = await this.seriesService.upsertSeriesAttendeePreferences(id, body.preferences, req, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  @Delete(':id/attendee-preferences/:userId')
  async deleteSeriesAttendeePreference(
    @Param('id') id: string,
    @Param('userId') userId: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const user = await this.requireAdminOrManager(req, res);
      if (!user) return res;
      const actor = req.user as any;
      const result = await this.seriesService.deleteSeriesAttendeePreference(id, userId, req, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Server error');
    }
  }

  private async requireMeetingUser(req: ExpressRequest, res: Response) {
    const user = req.user as
      | {
          userId?: string;
          id?: string;
          email?: string;
          roles?: Array<{ role?: { code?: string } } | string>;
          permissions?: string[];
        }
      | undefined;
    const userId = user?.userId ?? user?.id;
    if (!userId || !user?.email) {
      res.status(401).json({ error: 'Unauthorized access' });
      return null;
    }

    const role = getMeetingRoleFromUser(user);
    if (!role) {
      res.status(403).json({ error: 'Insufficient permissions' });
      return null;
    }

    return { id: userId, role, email: user.email, permissions: user.permissions ?? [] };
  }

  private async requireAdminOrManager(req: ExpressRequest, res: Response) {
    const meetingUser = await this.requireMeetingUser(req, res);
    if (!meetingUser) {
      return null;
    }

    if (!isMeetingAdminRole(meetingUser.role)) {
      res.status(403).json({ error: 'Insufficient permissions' });
      return null;
    }

    return meetingUser;
  }
}
