import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import {
  MockTestAttempts,
  AttemptStatus,
} from './entities/mock_test_attempts.entity';
import { CandidateAnswer } from '../candidate_answers/entities/candidate_answers.entity';
import { QuestionBank } from '../question_bank/entities/question_bank.entity';
import { McqOptions } from '../mcq_options/entities/mcq_options.entity';
import { CandidateFeedback } from '../candidate_feedback/entities/candidate_feedback.entity';
import { CandidateFeedbackService } from '../candidate_feedback/candidate_feedback.service';

@Injectable()
export class MockTestAttemptsService {
  constructor(
    @InjectRepository(MockTestAttempts)
    private attemptsRepository: Repository<MockTestAttempts>,
    @InjectRepository(CandidateAnswer)
    private answersRepository: Repository<CandidateAnswer>,
    @InjectRepository(QuestionBank)
    private questionRepository: Repository<QuestionBank>,
    @InjectRepository(McqOptions)
    private optionsRepository: Repository<McqOptions>,
    @InjectRepository(CandidateFeedback)
    private feedbackRepository: Repository<CandidateFeedback>,
    private candidateFeedbackService: CandidateFeedbackService,
  ) {}

  async submitTest(candidateId: number, data: any) {
    try {
      // console.log(`[MockTest] Submitting test for candidate ${candidateId}`, JSON.stringify(data));

      const { roleId, answers } = data;

      if (!answers || !Array.isArray(answers)) {
        throw new Error("Invalid payload: 'answers' must be an array.");
      }

      // 1. Create Attempt Record
      const attempt = this.attemptsRepository.create({
        candidate_id: candidateId,
        role_id: roleId,
        started_at: new Date(),
        completed_at: new Date(),
        status: AttemptStatus.COMPLETED,
      });

      const savedAttempt = await this.attemptsRepository.save(attempt);
      // console.log(`[MockTest] Saved attempt ID: ${savedAttempt.id}`);

      // 2. Fetch all questions to verify answers
      const questionIds = answers.map((a: any) => a.questionId);
      // Fetch questions with options to check correctness
      const questions = await this.questionRepository.find({
        where: { id: In(questionIds) },
        relations: ['options'],
      });

      const questionMap = new Map(questions.map((q) => [q.id, q]));

      let correctCount = 0;
      const candidateAnswers: CandidateAnswer[] = [];

      // 3. Process Answers
      for (const ans of answers) {
        const question = questionMap.get(ans.questionId);
        if (!question) {
          console.warn(`[MockTest] Question not found: ${ans.questionId}`);
          continue;
        }

        // Find the correct option for this question
        const correctOption = question.options.find((opt) => opt.is_correct);

        const isCorrect =
          correctOption && correctOption.id === ans.selectedOptionId;
        if (isCorrect) correctCount++;

        const candidateAnswer = this.answersRepository.create({
          attempt_id: savedAttempt.id,
          candidate_id: candidateId,
          question_id: ans.questionId,
          selected_option_id: ans.selectedOptionId,
          is_correct: !!isCorrect,
        });
        candidateAnswers.push(candidateAnswer);
      }

      // 4. Save Answers
      if (candidateAnswers.length > 0) {
        await this.answersRepository.save(candidateAnswers);
      }

      // 5. Update Attempt with Score
      savedAttempt.total_questions = answers.length;
      savedAttempt.correct_answers = correctCount;

      // Prevent NaN if answers.length is 0
      savedAttempt.score =
        answers.length > 0 ? (correctCount / answers.length) * 100 : 0;

      savedAttempt.metadata = {
        total_questions_in_db: questions.length,
      };

      await this.attemptsRepository.save(savedAttempt);
      // console.log(`[MockTest] Updated attempt with score. ID: ${savedAttempt.id}`);

      // 6. Generate Feedback based on topic performance
      const feedback = await this.calculateFeedback(
        savedAttempt.id,
        answers,
        questions,
        savedAttempt.score,
      );

      // Return plain object to ensure serialization works as expected
      return {
        ...JSON.parse(JSON.stringify(savedAttempt)),
        feedback,
      };
    } catch (error) {
      console.error(`[MockTest] Error submitting test:`, error);
      throw error; // Re-throw so NestJS handles it (500) but now we have logs
    }
  }

  private async calculateFeedback(
    attemptId: number,
    userAnswers: any[],
    questions: QuestionBank[],
    overallScore: number,
  ) {
    // Determine performance per topic
    const topicStats: Record<string, { total: number; correct: number }> = {};
    const questionMap = new Map(questions.map((q) => [q.id, q]));

    // Identify questions with missing topics
    const missingTopicQuestions = questions.filter(
      (q) =>
        !q.topic_tags ||
        (Array.isArray(q.topic_tags) && q.topic_tags.length === 0) ||
        (typeof q.topic_tags === 'string' &&
          (!q.topic_tags.trim() ||
            q.topic_tags === '[]' ||
            q.topic_tags === 'Uncategorized')),
    );

    if (missingTopicQuestions.length > 0) {
      console.log(
        `[MockTest] Inferring topics for ${missingTopicQuestions.length} questions...`,
      );
      const inferredMap =
        await this.candidateFeedbackService.inferTopicsForQuestions(
          missingTopicQuestions.map((q) => ({
            id: q.id,
            question: q.question_text,
          })),
        );

      // Update questions locally and in DB
      const updates = [];
      for (const q of missingTopicQuestions) {
        if (inferredMap.has(q.id)) {
          const newTopics = inferredMap.get(q.id);
          const newTopicsJson = JSON.stringify(newTopics);
          q.topic_tags = newTopicsJson; // Update local object as JSON string

          // Prepare DB update
          updates.push(
            this.questionRepository.update(q.id, { topic_tags: newTopicsJson }),
          );
        }
      }
      // Execute DB updates in background
      Promise.all(updates).catch((e) =>
        console.error('Failed to save inferred topics', e),
      );
    }

    for (const ans of userAnswers) {
      const question = questionMap.get(ans.questionId);
      if (!question) continue;

      let topics: string[] = [];
      try {
        if (question.topic_tags) {
          // Handle if it's already an array or a JSON string (runtime check)
          if (Array.isArray(question.topic_tags as any)) {
            topics = question.topic_tags as any;
          } else if (typeof question.topic_tags === 'string') {
            const trimmed = question.topic_tags.trim();
            if (trimmed.startsWith('[')) {
              topics = JSON.parse(trimmed);
            } else {
              topics = [trimmed];
            }
          }
        }
      } catch (e) {
        console.warn(
          `Failed to parse topic_tags for question ${question.id}`,
          e,
        );
        topics = ['Uncategorized'];
      }

      if (topics.length === 0) topics = ['Uncategorized'];

      const selectedOption = question.options.find(
        (o) => o.id === ans.selectedOptionId,
      );
      const isCorrect = selectedOption?.is_correct || false;

      for (const topic of topics) {
        if (!topicStats[topic]) {
          topicStats[topic] = { total: 0, correct: 0 };
        }
        topicStats[topic].total++;
        if (isCorrect) {
          topicStats[topic].correct++;
        }
      }
    }

    // Generate Summary
    const topicPerformance = {};
    const strengths = [];
    const improvements = [];

    for (const [topic, stats] of Object.entries(topicStats)) {
      const percentage = (stats.correct / stats.total) * 100;
      topicPerformance[topic] = {
        ...stats,
        percentage: parseFloat(percentage.toFixed(2)),
      };

      if (percentage >= 70) {
        strengths.push(topic);
      } else {
        improvements.push(topic);
      }
    }

    const totalCorrect = Object.values(topicStats).reduce(
      (sum, s) => sum + s.correct,
      0,
    );
    const totalQuestions = Object.values(topicStats).reduce(
      (sum, s) => sum + s.total,
      0,
    );
    const overallPercentage =
      totalQuestions > 0 ? (totalCorrect / totalQuestions) * 100 : 0;

    const feedbackJson = {
      topic_performance: topicPerformance,
      strengths: strengths,
      improvement_areas: improvements,
      overall_comment: `You scored ${Math.round(overallScore)}% overall.`,
    };

    // Save to DB
    const feedbackEntry = this.feedbackRepository.create({
      attempt_id: attemptId,
      feedback: feedbackJson,
    });

    // try {
    //   const tableInfo = await this.feedbackRepository.query('DESCRIBE candidate_feedback');
    //   console.log('[Debug] candidate_feedback schema:', JSON.stringify(tableInfo));
    // } catch (e) {
    //   console.error('[Debug] Failed to describe table:', e);
    // }

    await this.feedbackRepository.save(feedbackEntry);

    // 7. Generate AI Feedback
    // Await to show on frontend immediately
    const aiFeedback = await this.candidateFeedbackService.generateAiFeedback(
      feedbackEntry.id,
      feedbackJson,
      overallScore,
    );

    if (aiFeedback) {
      Object.assign(feedbackJson, aiFeedback);
    }

    return feedbackJson;
  }
}
