import { injectable, inject } from 'inversify';
import {
  JsonController,
  Authorized,
  Post,
  Get,
  Delete,
  Body,
  Params,
  HttpCode,
  UploadedFile,
  OnUndefined,
  QueryParams,
  ForbiddenError,
} from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { AnomalyService } from '../services/AnomalyService.js';
import { BadRequestErrorResponse, InternalServerErrorResponse } from '#shared/middleware/errorHandler.js';
import { ANOMALIES_TYPES } from '../types.js';
import { audioUploadOptions, imageUploadOptions } from '../classes/validators/fileUploadOptions.js';
import { AnomalyData, AnomalyIdParams, CourseAnomaliesQuery, DeleteAnomalyBody, GetAnomalyParams, GetCourseAnomalyParams, GetItemAnomalyParams, GetUserAnomalyParams, NewAnomalyData, StatsQueryParams } from '../classes/validators/AnomalyValidators.js';
import { AnomalyDataResponse, AnomalyStats, AnomalyType, FileType } from '../classes/transformers/Anomaly.js';
import { SETTING_TYPES } from '#root/modules/setting/types.js';
import { CourseSettingService } from '#root/modules/setting/services/CourseSettingService.js';
import { ProctoringComponent } from '#root/shared/database/interfaces/ISettingRepository.js';
import { PaginationQuery } from '#root/shared/index.js';
import { Ability } from '#root/shared/functions/AbilityDecorator.js';
import { getAnomalyAbility } from '../abilities/anomalyAbilities.js';
import { subject } from '@casl/ability';
import { PaginatedResponse } from '../classes/transformers/Anomaly.js';
import { UserNotFoundErrorResponse } from '#root/modules/users/classes/index.js';

@OpenAPI({
  tags: ['Anomalies'],
  description: 'Operations for managing anomaly detection with encrypted image storage',
})
@injectable()
@JsonController('/anomalies')
export class AnomalyController {
  constructor(
    @inject(ANOMALIES_TYPES.AnomalyService) private anomalyService: AnomalyService,
    @inject(SETTING_TYPES.CourseSettingService) private courseSettingService: CourseSettingService,
  ) {}

  @OpenAPI({
    summary: 'Record anomaly image',
    description: 'Records an anomaly image stored in cloud storage.',
  })
  @Post('/record/image')
  @HttpCode(201)
  @Authorized()
  @ResponseSchema(AnomalyData, {
    description: 'Anomaly recorded successfully',
  })
  @ResponseSchema(BadRequestErrorResponse, {
    description: 'Bad Request Error',
    statusCode: 400,
  })
  async recordImageAnomaly(
    // @UploadedFile("image", {required:true, options: imageUploadOptions })
    @UploadedFile("image", {options: imageUploadOptions })
      file: Express.Multer.File,
    @Body() body: NewAnomalyData,
    // @Ability(getAnomalyAbility) {ability,user} as it not giving permisson to post it
    @Ability(getAnomalyAbility) {user}
  ): Promise<AnomalyData> {
    const { courseId, versionId } = body;
    const userId = user._id.toString();
    const anomalyRes = subject('Anomaly', { courseId, versionId });

    // commented below as it is not allowing to post "anomalies/record/image" endpoint
    // if (!ability.can('create', anomalyRes)) {
    //   throw new ForbiddenError('You do not have permission to create an anomaly');
    // }

    if (body.type === AnomalyType.FACE_RECOGNITION) {
      const courseSetting = await this.courseSettingService.readCourseSettings(
        courseId.toString(),
        versionId.toString(),
      );
      const detector = courseSetting?.settings?.proctors?.detectors?.find(
        d => d.detectorName === ProctoringComponent.FACERECOGNITION,
      );
      if (!detector?.settings?.enabled) {
        throw new ForbiddenError('Face recognition is disabled for this course');
      }
    }

    return this.anomalyService.recordAnomaly(userId, body, file, FileType.IMAGE);
  }

  @OpenAPI({
    summary: 'Record anomaly with audio',
    description: 'Records an anomaly udio stored in cloud storage.',
  })
  @Post('/record/audio')
  @HttpCode(201)
  @Authorized()
  @ResponseSchema(AnomalyData, {
    description: 'Anomaly recorded successfully',
  })
  @ResponseSchema(BadRequestErrorResponse, {
    description: 'Bad Request Error',
    statusCode: 400,
  })
  async recordAudioAnomaly(
    @UploadedFile("audio", { required: true, options: audioUploadOptions })
      file: Express.Multer.File,
    @Body() body: NewAnomalyData,
    @Ability(getAnomalyAbility) {ability, user}
  ): Promise<AnomalyData> {
    const { courseId, versionId } = body;
    const userId = user._id.toString();

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('create', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to create an anomaly');
    }

    return this.anomalyService.recordAnomaly(userId, body, file, FileType.AUDIO);
  }

  @OpenAPI({
    summary: 'Get a particular anomaly',
    description: 'Retrieves a specific anomaly for a user',
  })
  @Get('/:anomalyId/course/:courseId/version/:versionId')
  @Authorized()
  @ResponseSchema(AnomalyDataResponse,{
    description: 'Anomaly retrieved successfully',
    statusCode: 200,
  })
  @ResponseSchema(UserNotFoundErrorResponse, {
    description: 'User not found',
    statusCode: 404,
  })
  @ResponseSchema(InternalServerErrorResponse, {
    description: 'Could not Fetch the Anomaly',
    statusCode: 500,
  })
  async getAnomaly(
    @Params() params: GetAnomalyParams,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<AnomalyDataResponse> {
    const { courseId, versionId, anomalyId } = params;

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('view', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to view this anomaly');
    }
    const anomaly = await this.anomalyService.findAnomalyById(anomalyId, courseId, versionId);
    return anomaly;
  }

  @OpenAPI({
    summary: 'Get user anomalies with filtering and pagination',
    description: 'Retrieves anomalies for a specific user with optional filtering by course, module, section, item, type, date range, and pagination support. Captures Accept header for potential content negotiation.',
  })
  @Get('/course/:courseId/version/:versionId/user/:userId')
  @Authorized()
  @ResponseSchema(AnomalyData,{
    description: 'Anomalies retrieved successfully',
    statusCode: 200,
  })
  @ResponseSchema(UserNotFoundErrorResponse, {
    description: 'User not found',
    statusCode: 404,
  })
  @ResponseSchema(InternalServerErrorResponse, {
    description: 'Could not Fetch the Anomalies',
    statusCode: 500,
  })
  async getUserAnomalies(
    @Params() params: GetUserAnomalyParams,
    @QueryParams() query: PaginationQuery,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<AnomalyData[]> {
    const { courseId, versionId, userId } = params;
    const { page, limit } = query;
    const skip = (page - 1) * limit;

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('view', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to view anomalies for this user');
    }
    const anomalies = await this.anomalyService.getUserAnomalies(userId, courseId, versionId, limit, skip);

    return anomalies;
  }

  @OpenAPI({
    summary: 'Get course anomalies',
    description: 'Retrieves all anomalies for a specific course with optional sorting and pagination',
  })
  @Get('/course/:courseId/version/:versionId')
  @Authorized()
  @ResponseSchema(PaginatedResponse, { isArray: false ,
    description: 'Anomalies retrieved successfully',
    statusCode: 200,})
  @ResponseSchema(InternalServerErrorResponse, {
    description: 'Could not Fetch the Anomalies',
    statusCode: 500,
  })
  async getCourseAnomalies(
    @Params() params: GetCourseAnomalyParams,
    @QueryParams() query: CourseAnomaliesQuery,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<PaginatedResponse<AnomalyData>> {
    const { courseId, versionId } = params;
    const { page = 1, limit = 10, sortField, sortOrder, search, type , cohort } = query;
    const skip = (page - 1) * limit;

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('view', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to view anomalies for this course');
    }

    const sortOptions = sortField ? { field: sortField, order: sortOrder } : undefined;
    return this.anomalyService.getCourseAnomalies(
      courseId, 
      versionId, 
      limit, 
      skip, 
      sortOptions, 
      search,
      type,
      page,
      cohort
    );
  }

  @OpenAPI({
  summary: 'Get Item anomalies',
  description: 'Retrieves all anomalies for a specific item',
  })
  @Get('/course/:courseId/version/:versionId/item/:itemId')
  @Authorized()
  @ResponseSchema(AnomalyData,{
    description: 'Anomalies retrieved successfully',
    statusCode: 200,
  })
  @ResponseSchema(InternalServerErrorResponse, {
    description: 'Could not Fetch the Anomalies',
    statusCode: 500,
  })
  async getItemAnomalies(
    @Params() params: GetItemAnomalyParams,
    @QueryParams() query: PaginationQuery,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<AnomalyData[]> {
    const { courseId, versionId, itemId } = params;
    const { page, limit } =  query
    const skip = (page - 1) * limit;

    const anomalyRes = subject('Anomaly', { courseId, versionId, itemId });
    if (!ability.can('view', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to view anomalies for this item');
    }

    const anomalies = await this.anomalyService.getCourseItemAnomalies(courseId, versionId, itemId, limit, skip);

    return anomalies;
  }

  @OpenAPI({
    summary: 'Get anomaly statistics',
    description: 'Retrieves statistics for a specific anomaly item',
  })
  @Get('/course/:courseId/version/:versionId/stats')
  @Authorized()
  @ResponseSchema(AnomalyStats,{
    description:'Anomaly statistics retrieved successfully',
    statusCode:200
  })
  async getAnomalyStats(
    @Params() params: GetCourseAnomalyParams,
    @QueryParams() query: StatsQueryParams,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<AnomalyStats> {
    const { courseId, versionId } = params;
    const { userId, itemId } = query;

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('view', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to view anomaly statistics for this course');
    }

    return this.anomalyService.getAnomalyStats(courseId, versionId, userId, itemId);
  }

  @OpenAPI({
    summary: 'Delete anomaly',
    description: `Deletes an anomaly record and its encrypted image<br/>
    It returns an empty body with a 200 status code.`,
    
  })
  @Delete('/:id')
  @Authorized()
  @OnUndefined(200)
  @ResponseSchema(BadRequestErrorResponse, {
    description: 'Bad Request Error',
    statusCode: 400,
  })
  async deleteAnomaly(
    @Params() params: AnomalyIdParams,
    @Body() body: DeleteAnomalyBody,
    @Ability(getAnomalyAbility) {ability}
  ): Promise<void> {
    const { courseId, versionId } = body;

    const anomalyRes = subject('Anomaly', { courseId, versionId });
    if (!ability.can('delete', anomalyRes)) {
      throw new ForbiddenError('You do not have permission to delete this anomaly');
    }

    await this.anomalyService.deleteAnomaly(params.id, courseId, versionId);
  }
}