import {
  Body,
  Controller,
  Get,
  Param,
  Patch,
  Post,
  Query,
  Request,
  Res,
  UseGuards,
} from '@nestjs/common';
import type { Request as ExpressRequest, Response } from 'express';
import { SkipTransform } from '@common/decorators/skip-transform.decorator';
import { handleMeetingAttendanceError } from '../errors/handle-controller-error';
import { getMeetingRoleFromUser, isMeetingAdminRole } from '../utils/meeting-roles';
import { OutlookSyncService } from '../services/outlook-sync.service';
import { OutlookSyncSchedulerService } from '../services/outlook-sync-scheduler.service';
import {
  ExcludeOutlookSeriesOccurrenceDto,
  ListOutlookBindingHistoryQueryDto,
  ListOutlookSeriesOccurrenceExclusionsQueryDto,
  ListManagedOutlookBindingsQueryDto,
  ListAllManagedOutlookBindingsQueryDto,
  ListOutlookCandidatesQueryDto,
  ListOutlookCandidateChildrenQueryDto,
  ListManagedSeriesChildrenQueryDto,
  ManageOutlookBindingDto,
  TakeoverOutlookBindingDto,
  UpdateOutlookSyncSettingsDto,
} from '../dto/outlook-sync.dto';

@Controller('meeting-attendance/integrations/outlook')
@SkipTransform()
export class MeetingAttendanceOutlookSyncController {
  constructor(
    private readonly outlookSyncService: OutlookSyncService,
    private readonly outlookSyncSchedulerService: OutlookSyncSchedulerService,
  ) {}

  @Get('candidates')
  async listCandidates(
    @Query() query: ListOutlookCandidatesQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const rawQuery = req.query as Record<string, unknown>;
      const result = await this.outlookSyncService.listCandidates({
        ...query,
        includeCancelled: rawQuery.includeCancelled as any,
        includePast: rawQuery.includePast as any,
        onlyUnmanaged: rawQuery.onlyUnmanaged as any,
      }, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list Outlook candidates', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('candidates/:seriesMasterId/children')
  async listCandidateSeriesChildren(
    @Param('seriesMasterId') seriesMasterId: string,
    @Query() query: ListOutlookCandidateChildrenQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const rawQuery = req.query as Record<string, unknown>;
      const result = await this.outlookSyncService.listCandidateSeriesChildren(seriesMasterId, {
        ...query,
        includeCancelled: rawQuery.includeCancelled as any,
        includePast: rawQuery.includePast as any,
      }, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list Outlook candidate series children', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('bindings')
  async manageBinding(
    @Body() body: ManageOutlookBindingDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.manageBinding(body, actor);
      return res.status(201).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to manage Outlook binding', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('bindings/:id/takeover')
  async takeoverBinding(
    @Param('id') id: string,
    @Body() body: TakeoverOutlookBindingDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.takeoverBinding(id, body, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to takeover Outlook binding', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('bindings/:id/unmanage')
  async unmanageBinding(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.unmanageBinding(id, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to unmanage Outlook binding', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('bindings/:id/resume-sync')
  async resumeSync(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.resumeSync(id, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to resume Outlook sync', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/:id/exclusions')
  async listBindingOccurrenceExclusions(
    @Param('id') id: string,
    @Query() query: ListOutlookSeriesOccurrenceExclusionsQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.listBindingOccurrenceExclusions(id, query);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list occurrence exclusions', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('bindings/:id/exclusions')
  async excludeSeriesOccurrence(
    @Param('id') id: string,
    @Body() body: ExcludeOutlookSeriesOccurrenceDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.excludeSeriesOccurrence(id, body, actor);
      return res.status(201).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to exclude series occurrence', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('exclusions/:id/remove')
  async removeSeriesOccurrenceExclusion(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.removeSeriesOccurrenceExclusion(id, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to remove occurrence exclusion', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings')
  async listManagedBindings(
    @Query() query: ListManagedOutlookBindingsQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.listManagedBindings(query);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list managed bindings', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/series/:seriesMasterId/children')
  async listManagedSeriesChildren(
    @Param('seriesMasterId') seriesMasterId: string,
    @Query() query: ListManagedSeriesChildrenQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.listManagedSeriesChildren(seriesMasterId, query, actor);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list managed series children', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/all')
  async listAllManagedBindings(
    @Query() query: ListAllManagedOutlookBindingsQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdministrator(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.listAllManagedBindings(query);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list all managed bindings', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/:id')
  async getBindingDetail(
    @Param('id') id: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.getBindingDetail(id);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to get binding detail', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/:id/history')
  async getBindingHistory(
    @Param('id') id: string,
    @Query() query: ListOutlookBindingHistoryQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.getBindingHistory(id, query);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to get binding history', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('bindings/:id/history/export.csv')
  async exportBindingHistoryCsv(
    @Param('id') id: string,
    @Query() query: ListOutlookBindingHistoryQueryDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.exportBindingHistoryCsv(id, query);
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader('Content-Disposition', `attachment; filename=\"${result.filename}\"`);
      return res.status(200).send(result.content);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to export binding history', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Post('sync/reconcile')
  async triggerReconcile(
    @Body() body: { mailboxId?: string },
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.triggerReconcile(body?.mailboxId);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to trigger reconcile', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Get('settings')
  async getSettings(
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.getSettings();
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to get sync settings', 'MeetingAttendanceOutlookSyncController');
    }
  }

  @Patch('settings')
  async updateSettings(
    @Body() body: UpdateOutlookSyncSettingsDto,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireAdminOrManager(req, res);
      if (!actor) return res;
      const result = await this.outlookSyncService.updateSettings(body);
      await this.outlookSyncSchedulerService.reloadReconcileJob();
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to update sync settings', 'MeetingAttendanceOutlookSyncController');
    }
  }

  private requireAdminOrManager(req: ExpressRequest, res: Response) {
    const user = req.user as
      | { userId?: string; id?: string; email?: string; roles?: Array<{ role?: { code?: string } } | 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 (!isMeetingAdminRole(role)) {
      res.status(403).json({ error: 'Insufficient permissions' });
      return null;
    }

    return { id: userId, role, email: user.email };
  }

  private requireAdministrator(req: ExpressRequest, res: Response) {
    const actor = this.requireAdminOrManager(req, res);
    if (!actor) {
      return null;
    }
    if (actor.role !== 'Administrator') {
      res.status(403).json({ error: 'Administrator role required' });
      return null;
    }
    return actor;
  }
}
