/**
 * Exercise Controller
 *
 * HTTP request handlers for exercise endpoints
 */

import { Request, Response } from 'express';
import { exerciseService } from './exercise.service';
import { ValidationError, AuthenticationError } from '../../shared/types';
import { asyncHandler } from '../../shared/middleware/validation.middleware';
import type { ExerciseType, ExerciseDifficulty } from '@prisma/client';
import type { ExerciseAttemptInput } from '../../shared/types';

// ============================================
// VALIDATION CONSTANTS
// ============================================

const ALLOWED_SORT_FIELDS = ['order', 'difficulty', 'points', 'createdAt'] as const;
const ALLOWED_SORT_ORDERS = ['asc', 'desc'] as const;

type SortField = typeof ALLOWED_SORT_FIELDS[number];
type SortOrder = typeof ALLOWED_SORT_ORDERS[number];

// ============================================
// CONTROLLER
// ============================================

class ExerciseController {
  /**
   * GET /api/exercises
   * List exercises with optional filtering
   */
  listExercises = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const {
      moduleId,
      topicId,
      type,
      difficulty,
      isPublished = 'true',
      page = '1',
      limit = '50',
      sortBy = 'order',
      sortOrder = 'asc',
    } = req.query;

    // Validate sortBy against whitelist
    const sortByValue = (sortBy as string) || 'order';
    if (!ALLOWED_SORT_FIELDS.includes(sortByValue as SortField)) {
      throw new ValidationError(
        `Invalid sortBy field. Allowed fields: ${ALLOWED_SORT_FIELDS.join(', ')}`
      );
    }

    // Validate sortOrder against whitelist
    const sortOrderValue = (sortOrder as string) || 'asc';
    if (!ALLOWED_SORT_ORDERS.includes(sortOrderValue as SortOrder)) {
      throw new ValidationError(
        `Invalid sortOrder. Allowed values: ${ALLOWED_SORT_ORDERS.join(', ')}`
      );
    }

    const pageNum = parseInt(page as string, 10) || 1;
    const limitNum = parseInt(limit as string, 10) || 50;
    const skip = (pageNum - 1) * limitNum;

    const filters: any = {
      isPublished: isPublished === 'true',
    };

    if (moduleId) filters.moduleId = moduleId as string;
    if (topicId) filters.topicId = topicId as string;
    if (type) filters.type = type as ExerciseType;
    if (difficulty) filters.difficulty = difficulty as ExerciseDifficulty;

    const { exercises, total } = await exerciseService.listExercises(filters, {
      skip,
      take: limitNum,
      orderBy: sortByValue as SortField,
      orderDirection: sortOrderValue as SortOrder,
    });

    const totalPages = Math.ceil(total / limitNum);

    res.json({
      success: true,
      data: exercises,
      meta: {
        pagination: {
          page: pageNum,
          limit: limitNum,
          total,
          totalPages,
          hasNext: pageNum < totalPages,
          hasPrev: pageNum > 1,
        },
      },
    });
  });

  /**
   * GET /api/exercises/:id
   * Get exercise by ID
   */
  getExerciseById = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }
    const {
      includeModule = 'false',
      includeTopic = 'false',
      includeUserAttempts = 'false',
      hideSolution = 'true',
    } = req.query;

    const exercise = await exerciseService.getExerciseById(id, {
      includeModule: includeModule === 'true',
      includeTopic: includeTopic === 'true',
      includeUserAttempts: includeUserAttempts === 'true',
      userId: req.user?.userId,
      hideSolution: hideSolution !== 'false',
    });

    res.json({
      success: true,
      data: exercise,
    });
  });

  /**
   * POST /api/exercises/:id/attempt
   * Submit an exercise attempt
   */
  submitAttempt = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }
    const userId = req.user?.userId;

    if (!userId) {
      throw new AuthenticationError('User authentication required');
    }

    const attemptData: ExerciseAttemptInput = {
      answer: req.body.answer,
      timeSpent: req.body.timeSpent || 0,
      hintsUsed: req.body.hintsUsed || 0,
      skipped: req.body.skipped || false,
    };

    // Validate required fields
    if (!attemptData.skipped && !attemptData.answer) {
      throw new ValidationError('answer is required when not skipping');
    }

    if (attemptData.answer && (attemptData.answer.includes('<script') || attemptData.answer.includes('javascript:'))) {
      throw new ValidationError('Invalid characters inside answer');
    }

    if (typeof attemptData.timeSpent !== 'number' || attemptData.timeSpent < 0) {
      throw new ValidationError('timeSpent must be a non-negative number');
    }

    const result = await exerciseService.submitAttempt(id, userId, attemptData);

    res.json({
      success: true,
      data: result,
    });
  });

  /**
   * GET /api/exercises/:id/solution
   * Get exercise solution
   */
  getExerciseSolution = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }

    const solution = await exerciseService.getExerciseSolution(
      id,
      req.user?.userId
    );

    res.json({
      success: true,
      data: solution,
    });
  });

  /**
   * GET /api/exercises/:id/hints
   * Get exercise hints
   */
  getExerciseHints = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }

    const hints = await exerciseService.getExerciseHints(id);

    res.json({
      success: true,
      data: hints,
    });
  });

  /**
   * GET /api/exercises/:id/attempts
   * Get user's attempts for an exercise with pagination
   */
  getUserAttempts = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }
    const userId = req.user?.userId;

    if (!userId) {
      throw new AuthenticationError('User authentication required');
    }

    const { page = '1', limit = '10' } = req.query;
    const pageNum = parseInt(page as string, 10) || 1;
    const limitNum = parseInt(limit as string, 10) || 10;

    // Validate pagination params
    if (pageNum < 1) {
      throw new ValidationError('page must be greater than 0');
    }
    if (limitNum < 1 || limitNum > 100) {
      throw new ValidationError('limit must be between 1 and 100');
    }

    const result = await exerciseService.getUserAttempts(id, userId, {
      page: pageNum,
      limit: limitNum,
    });

    res.json({
      success: true,
      data: result.attempts,
      meta: {
        totalAttempts: result.totalAttempts,
        bestScore: result.bestScore,
        hasCompleted: result.hasCompleted,
        pagination: result.pagination,
      },
    });
  });

  /**
   * GET /api/exercises/:id/next
   * Get next exercise in sequence
   */
  getNextExercise = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { id } = req.params;
    if (!id) {
      throw new ValidationError('Exercise ID is required');
    }

    const nextExercise = await exerciseService.getNextExercise(id);

    if (!nextExercise) {
      res.status(404).json({
        success: false,
        error: {
          code: 'NOT_FOUND',
          message: 'No next exercise found',
        },
      });
      return;
    }

    res.json({
      success: true,
      data: nextExercise,
    });
  });

  /**
   * GET /api/module/:moduleId/practice
   * Get exercises for practice mode
   */
  getPracticeExercises = asyncHandler(async (req: Request, res: Response): Promise<void> => {
    const { moduleId } = req.params;
    if (!moduleId) {
      throw new ValidationError('Module ID is required');
    }
    const userId = req.user?.userId;

    if (!userId) {
      throw new AuthenticationError('User authentication required');
    }

    const {
      difficulty,
      count = '10',
      excludeCompleted = 'false',
    } = req.query;

    const exercises = await exerciseService.getModuleExercisesForPractice(
      moduleId,
      userId,
      {
        difficulty: difficulty as ExerciseDifficulty,
        count: parseInt(count as string, 10) || 10,
        excludeCompleted: excludeCompleted === 'true',
      }
    );

    res.json({
      success: true,
      data: exercises,
    });
  });
}

// ============================================
// EXPORT
// ============================================

export const exerciseController = new ExerciseController();
export default exerciseController;
