/**
 * Exercise Repository Implementation
 * 
 * Prisma-based implementation of Exercise Repository.
 * Includes caching support and query optimization.
 */

import { PrismaClient, Exercise, Prisma } from '@prisma/client';
import {
  IExerciseRepository,
  ExerciseFilterOptions,
  ExerciseWithStats,
} from './interfaces/exercise.repository.interface';
import type { PaginatedResult, QueryOptions } from '@/core/types';
import { NotFoundError } from '@/core/errors';
import { logger } from '@/shared/utils/logger';

/**
 * Prisma Exercise Repository
 */
export class ExerciseRepository implements IExerciseRepository {
  constructor(
    private readonly prisma: PrismaClient,
    private readonly repoLogger: typeof logger
  ) {}

  async findById(
    id: string,
    options?: QueryOptions
  ): Promise<Exercise | null> {
    const exercise = await this.prisma.exercise.findUnique({
      where: { id },
      include: this.buildInclude(options) || null,
    });

    return exercise;
  }

  async findWithDetails(
    id: string,
    includeSolution = false
  ): Promise<Exercise | null> {
    const exercise = await this.prisma.exercise.findUnique({
      where: { id },
      include: {
        modules: true,
        topics: true,
        exercise_attempts: {
          where: { status: 'CORRECT' },
          select: {
            id: true,
            userId: true,
            pointsEarned: true,
            createdAt: true,
          },
          take: 5,
          orderBy: { pointsEarned: 'desc' },
        },
      },
    });

    if (!exercise) {
      return null;
    }

    // Remove solution if not requested
    if (!includeSolution) {
      const { correctAnswer, solutionSteps, ...rest } = exercise;
      return rest as Exercise;
    }

    return exercise;
  }

  async findMany(
    filters: ExerciseFilterOptions,
    page: number,
    limit: number
  ): Promise<PaginatedResult<Exercise>> {
    const where = this.buildWhereClause(filters);
    const skip = (page - 1) * limit;

    const [exercises, total] = await Promise.all([
      this.prisma.exercise.findMany({
        where,
        skip,
        take: limit,
        orderBy: { order: 'asc' },
      }),
      this.prisma.exercise.count({ where }),
    ]);

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

    return {
      items: exercises,
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  async findByModule(
    moduleId: string,
    options?: { includeUnpublished?: boolean }
  ): Promise<Exercise[]> {
    return this.prisma.exercise.findMany({
      where: {
        moduleId,
        ...(options?.includeUnpublished ? {} : { isPublished: true }),
      },
      orderBy: { order: 'asc' },
    });
  }

  async create(data: Prisma.ExerciseCreateInput): Promise<Exercise> {
    const exercise = await this.prisma.exercise.create({ data });
    
    this.repoLogger.info({
      exerciseId: exercise.id,
      moduleId: exercise.moduleId,
      title: exercise.statement.substring(0, 50),
    }, 'Exercise created');

    return exercise;
  }

  async update(
    id: string,
    data: Prisma.ExerciseUpdateInput
  ): Promise<Exercise> {
    try {
      const exercise = await this.prisma.exercise.update({
        where: { id },
        data,
      });

      this.repoLogger.info({ exerciseId: id }, 'Exercise updated');
      return exercise;
    } catch (error) {
      if (error instanceof Prisma.PrismaClientKnownRequestError) {
        if (error.code === 'P2025') {
          throw new NotFoundError('Exercise', {}, undefined);
        }
      }
      throw error;
    }
  }

  async delete(id: string, hardDelete = false): Promise<void> {
    if (hardDelete) {
      await this.prisma.exercise.delete({ where: { id } });
    } else {
      await this.prisma.exercise.update({
        where: { id },
        data: { isPublished: false },
      });
    }

    this.repoLogger.info({ exerciseId: id, hardDelete }, 'Exercise deleted');
  }

  async count(filters: ExerciseFilterOptions): Promise<number> {
    const where = this.buildWhereClause(filters);
    return this.prisma.exercise.count({ where });
  }

  async getStats(id: string): Promise<ExerciseWithStats | null> {
    const [exercise, stats] = await Promise.all([
      this.prisma.exercise.findUnique({ where: { id } }),
      this.prisma.exerciseAttempt.groupBy({
        by: ['status'],
        where: { exerciseId: id },
        _count: { id: true },
        _avg: { timeSpentSeconds: true },
      }),
    ]);

    if (!exercise) {
      return null;
    }

    const totalAttempts = stats.reduce((sum, s) => sum + s._count.id, 0);
    const correctAttempts =
      stats.find((s) => s.status === 'CORRECT')?._count.id || 0;
    const successRate =
      totalAttempts > 0 ? (correctAttempts / totalAttempts) * 100 : 0;
    const averageTimeSpent =
      stats.length > 0
        ? stats.reduce((sum, s) => sum + (s._avg.timeSpentSeconds || 0), 0) /
          stats.length
        : 0;

    return {
      ...exercise,
      totalAttempts,
      correctAttempts,
      successRate,
      averageTimeSpent,
    };
  }

  async findNextInModule(
    moduleId: string,
    currentOrder: number
  ): Promise<Exercise | null> {
    return this.prisma.exercise.findFirst({
      where: {
        moduleId,
        order: { gt: currentOrder },
        isPublished: true,
      },
      orderBy: { order: 'asc' },
    });
  }

  async findPreviousInModule(
    moduleId: string,
    currentOrder: number
  ): Promise<Exercise | null> {
    return this.prisma.exercise.findFirst({
      where: {
        moduleId,
        order: { lt: currentOrder },
        isPublished: true,
      },
      orderBy: { order: 'desc' },
    });
  }

  // ============================================
  // Private Helpers
  // ============================================

  private buildWhereClause(filters: ExerciseFilterOptions): Prisma.ExerciseWhereInput {
    const where: Prisma.ExerciseWhereInput = {};

    if (filters.moduleId !== undefined) {
      where.moduleId = filters.moduleId;
    }

    if (filters.topicId !== undefined) {
      where.topicId = filters.topicId;
    }

    if (filters.type !== undefined) {
      where.type = filters.type;
    }

    if (filters.difficulty !== undefined) {
      where.difficulty = filters.difficulty;
    }

    if (filters.isPublished !== undefined) {
      where.isPublished = filters.isPublished;
    }

    if (filters.isAIGenerated !== undefined) {
      where.isAIGenerated = filters.isAIGenerated;
    }

    return where;
  }

  private buildInclude(
    options?: QueryOptions
  ): Prisma.ExerciseInclude | undefined {
    if (!options?.relations) {
      return undefined;
    }

    const include: Prisma.ExerciseInclude = {};

    for (const relation of options.relations) {
      if (relation === 'module') {
        include.modules = true;
      } else if (relation === 'topic') {
        include.topics = true;
      } else if (relation === 'attempts') {
        include.exercise_attempts = { take: 5 };
      }
    }

    return include;
  }
}
