import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserAiInterviews } from './entities/user_ai_interviews.entity';
import { UserAiAnswers } from '../user_ai_answers/entities/user_ai_answers.entity';
import { InterviewSkillSummary } from '../interview_skill_summary/entities/interview_skill_summary.entity';

import Groq from 'groq-sdk';

@Injectable()
export class UserAiInterviewsService {
  private groq: Groq;

  constructor(
    @InjectRepository(UserAiInterviews)
    private readonly interviewRepository: Repository<UserAiInterviews>,
    @InjectRepository(UserAiAnswers)
    private readonly answersRepository: Repository<UserAiAnswers>,
    @InjectRepository(InterviewSkillSummary)
    private readonly summaryRepository: Repository<InterviewSkillSummary>,
  ) {
    const apiKey = process.env.GROQ_API_KEY || '';
    if (apiKey) {
      this.groq = new Groq({ apiKey });
    }
  }

  async create(data: Partial<UserAiInterviews>): Promise<UserAiInterviews> {
    const interview = this.interviewRepository.create(data);
    return await this.interviewRepository.save(interview);
  }

  async findOne(id: number): Promise<UserAiInterviews> {
    return this.interviewRepository.findOne({ where: { id } });
  }

  async saveAnswer(data: Partial<UserAiAnswers>): Promise<UserAiAnswers> {
    // Check if answer already exists for this interview and question
    let existingAnswer = null;
    if (data.interview_id && data.question_id) {
      existingAnswer = await this.answersRepository.findOne({
        where: {
          interview_id: data.interview_id,
          question_id: data.question_id,
        },
      });
    }

    if (existingAnswer) {
      // Update existing
      const updated = this.answersRepository.merge(existingAnswer, data);
      return await this.answersRepository.save(updated);
    } else {
      // Create new
      const answer = this.answersRepository.create(data);
      return await this.answersRepository.save(answer);
    }
  }

  async update(id: number, data: Partial<UserAiInterviews>): Promise<void> {
    await this.interviewRepository.update(id, data);
  }

  async findActiveBySchedule(scheduleId: number): Promise<UserAiInterviews> {
    return this.interviewRepository.findOne({
      where: {
        schedule_id: scheduleId,
        status: 'in_progress',
      },
      order: { created_at: 'DESC' },
    });
  }

  async complete(id: number): Promise<UserAiInterviews> {
    const interview = await this.interviewRepository.findOne({ where: { id } });
    if (!interview) throw new Error('Interview not found');

    // Fetch Answers with Question Relations to access Skills/Topics
    const answers = await this.answersRepository.find({
      where: { interview_id: id },
      relations: ['question'], // Need to add relations in entity or use query builder if this fails
    });

    // --- SCORING LOGIC ---
    let totalQuestions = interview.total_questions || answers.length;
    let correctCount = 0;

    // Structure for Level 2 & 3
    const skillStats: Record<string, { total: number; correct: number }> = {};
    const topicStats: Record<string, { total: number; correct: number }> = {};

    // We need a role name for the Prompt. Assuming we can get it from JobPost or pass it.
    // For now, we'll try to infer or use generic if unavailable, but ideally we fetch JobPost.
    // Let's assume we can get basic info.

    // Fetch Schedule to get the full list of Question IDs (including unanswered ones)
    let allQuestionIds: number[] = [];
    if (interview.schedule_id) {
      const schedule = await this.interviewRepository.manager
        .createQueryBuilder()
        .select(['s.questions_list'])
        .from('ai_interview_schedules', 's')
        .where('s.id = :sid', { sid: interview.schedule_id })
        .getRawOne();

      if (schedule && schedule.s_questions_list) {
        try {
          // questions_list might be JSON string or array depending on DB driver/ORM
          allQuestionIds =
            typeof schedule.s_questions_list === 'string'
              ? JSON.parse(schedule.s_questions_list)
              : schedule.s_questions_list;
        } catch (e) {}
      }
    }

    // Fetch actual Question objects for ALL IDs (to get skills/tags for unanswered ones too)
    // If answers.length == totalQuestions, we might be fine, but for timeout we have fewer answers.

    // Map existing answers for quick lookup
    const answerMap = new Map<number, UserAiAnswers>();
    answers.forEach((a) => answerMap.set(a.question_id, a));

    // We need details for ALL questions to properly update stats
    // If allQuestionIds is empty (legacy), fallback to answers
    const idsToFetch =
      allQuestionIds.length > 0
        ? allQuestionIds
        : answers.map((a) => a.question_id);

    let allQuestions = [];
    if (idsToFetch.length > 0) {
      // We can't inject QuestionBankService here easily to avoid circular deps,
      // so we query the repo/table directly or assume we can rely on what we have.
      // Best approach: Query question_bank table directly via manager
      allQuestions = await this.interviewRepository.manager
        .getRepository('question_bank')
        .createQueryBuilder('q')
        .where('q.id IN (:...ids)', { ids: idsToFetch })
        .getMany();
    }

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

    // Initialize Granular Stats
    const detailedTopicStats: Record<
      string,
      {
        scheduled: number;
        attempted: number;
        correct: number;
        unattempted: number;
        accuracy: number;
      }
    > = {};
    let globalStats = {
      scheduled_questions: idsToFetch.length,
      attempted_questions: 0,
      correct_answers: 0,
      incorrect_answers: 0,
      unattempted_due_to_timeout: 0,
    };

    // Calculate Stats iterating over ALL expected questions
    idsToFetch.forEach((qId) => {
      const question = questionMap.get(qId);
      const answer = answerMap.get(qId);

      // Check if attempt made (has text OR options selected)
      const isAttempted = !!(
        answer &&
        (answer.answer_text ||
          (answer.chosen_option_ids && answer.chosen_option_ids.length > 0))
      );
      const isCorrect = answer?.is_correct || false;

      if (question) {
        // Determine Skill Name
        let skillName = 'General';
        if (question.topic_tags) {
          try {
            const tags =
              typeof question.topic_tags === 'string'
                ? JSON.parse(question.topic_tags)
                : question.topic_tags;
            if (tags && tags.length > 0) skillName = tags[0];
          } catch (e) {}
        }

        // Initialize detailed stats if missing
        if (!detailedTopicStats[skillName]) {
          detailedTopicStats[skillName] = {
            scheduled: 0,
            attempted: 0,
            correct: 0,
            unattempted: 0,
            accuracy: 0,
          };
        }
        detailedTopicStats[skillName].scheduled++;

        if (isAttempted) {
          detailedTopicStats[skillName].attempted++;
          globalStats.attempted_questions++;

          if (isCorrect) {
            detailedTopicStats[skillName].correct++;
            globalStats.correct_answers++;
            correctCount++;
          } else {
            globalStats.incorrect_answers++;
          }
        } else {
          detailedTopicStats[skillName].unattempted++;
          globalStats.unattempted_due_to_timeout++;
        }

        if (!skillStats[skillName])
          skillStats[skillName] = { total: 0, correct: 0 };
        skillStats[skillName].total++;
        if (isCorrect) skillStats[skillName].correct++;

        // Track Weak Topics (Legacy tracking kept for now, but effectively duplicated logic)
        const topic = skillName;
        if (!topicStats[topic]) topicStats[topic] = { total: 0, correct: 0 };
        topicStats[topic].total++;
        if (isCorrect) topicStats[topic].correct++;
      }
    });

    // Calculate Topic Accuracy for Detailed Stats
    Object.keys(detailedTopicStats).forEach((topic) => {
      const t = detailedTopicStats[topic];
      t.accuracy =
        t.scheduled > 0 ? Math.round((t.correct / t.scheduled) * 100) : 0;
    });

    // If using idsToFetch loop, correctCount is fully recalculated, so we can ignore lines 86 (let correctCount=0).
    totalQuestions = idsToFetch.length || totalQuestions;

    const overallScore =
      totalQuestions > 0 ? (correctCount / totalQuestions) * 100 : 0;

    // Calculate Skill Scores
    const skillScores = {};
    Object.keys(skillStats).forEach((skill) => {
      const stats = skillStats[skill];
      skillScores[skill] = Math.round((stats.correct / stats.total) * 100);
    });

    // Identify Weak/Strong Topics
    const weakTopics = [];
    const strongTopics = [];
    Object.keys(topicStats).forEach((topic) => {
      const stats = topicStats[topic];
      const pct = (stats.correct / stats.total) * 100;
      if (pct < 50) weakTopics.push(topic);
      if (pct >= 80) strongTopics.push(topic);
    });

    // Fetch Job Details for Prompt
    let roleName = 'Candidate Role';
    let experienceLevel = 'Entry Level';

    try {
      if (interview.schedule_id) {
        const scheduleData = await this.interviewRepository.manager
          .createQueryBuilder()
          .select([
            'job.title',
            'ai.ai_experience_level',
            'exp.level',
            'exp.duration',
          ])
          .from('ai_interview_schedules', 's')
          .leftJoin('job_posts', 'job', 'job.id = s.job_id')
          .leftJoin('job_post_ai_settings', 'ai', 'ai.job_post_id = job.id')
          .leftJoin('experiences', 'exp', 'exp.id = ai.ai_experience_level')
          .where('s.id = :sid', { sid: interview.schedule_id })
          .getRawOne();

        if (scheduleData) {
          if (scheduleData.job_title) roleName = scheduleData.job_title;

          // Construct detailed experience level
          if (scheduleData.exp_level) {
            experienceLevel = scheduleData.exp_level;
            if (scheduleData.exp_duration) {
              experienceLevel += ` (${scheduleData.exp_duration})`;
            }
          } else if (scheduleData.ai_ai_experience_level) {
            // Fallback to ID if join fails (unlikely if FK is valid)
            experienceLevel = `Level ID: ${scheduleData.ai_ai_experience_level}`;
          }
        }
      }
    } catch (err) {
      console.error('Failed to fetch job context for AI feedback:', err);
    }

    // Detect Termination Context
    const terminationReason =
      interview.submission_meta?.reason || 'Manual Submission';
    const isTimeout = terminationReason === 'Time Limit Reached';
    const terminationContext = {
      termination_type: isTimeout
        ? 'timeout'
        : interview.submission_meta?.reason
          ? 'abnormal'
          : 'normal',
      reason: terminationReason,
      completion_percentage:
        idsToFetch.length > 0
          ? Math.round(
              (globalStats.attempted_questions / idsToFetch.length) * 100,
            )
          : 0,
      attempted_questions: globalStats.attempted_questions,
      scheduled_questions: idsToFetch.length,
      has_unattempted_topics: globalStats.unattempted_due_to_timeout > 0,
    };

    // Deterministic Weakness/Status Analysis
    const topicAnalysis = Object.keys(detailedTopicStats).map((topic) => {
      const stats = detailedTopicStats[topic];
      let status = 'strong';
      let reason = 'none';

      if (stats.scheduled > 0) {
        if (stats.attempted === 0) {
          status = 'unassessed';
          reason = 'unassessed_due_to_timeout';
        } else {
          // Check accuracy on ATTEMPTED questions only
          const attemptedAccuracy =
            stats.attempted > 0 ? stats.correct / stats.attempted : 0;
          if (attemptedAccuracy < 0.5) {
            // Threshold 50%
            status = 'weak';
            reason = 'conceptual_gap';
          }
        }
      }
      return { topic, status, reason, stats };
    });

    // --- GENERATE AI FEEDBACK ---
    let aiFeedback = null;
    if (this.groq) {
      aiFeedback = await this.generateAiFeedback({
        roleName,
        experienceLevel,
        totalScore: Math.round(overallScore),
        skillScores,
        detailedTopicStats,
        globalStats,
        weakTopics,
        strongTopics,
        terminationContext,
        topicAnalysis, // Pass deterministic analysis
      });
    }

    // --- UPDATE INTERVIEW ---
    await this.interviewRepository.update(id, {
      status: 'completed',
      completed_at: new Date(),
      score: parseFloat(overallScore.toFixed(2)),
      skill_scores: skillScores,
      weak_topics: weakTopics,
      strong_topics: strongTopics,
      ai_feedback_json: aiFeedback,
      ai_session_metadata: {
        ...(interview.ai_session_metadata || {}),
        skill_breakdown: detailedTopicStats,
        global_stats: globalStats,
        termination_summary: terminationContext, // Persist for frontend use if needed
      },
    });

    // --- POPULATE SKILL SUMMARY (Granular) ---
    // Clear existing summaries for this interview first to avoid duplicates if re-run
    await this.summaryRepository.delete({ interview_id: id });

    const summaryPromises = Object.entries(skillStats).map(
      async ([skillName, stats]) => {
        // Try to find a skill_id if possible, or just save generic for now.
        // Since we don't have a direct Skill Name -> ID map here without querying Skills table,
        // we will leave skill_id null or try to infer if we had it.
        // We'll save the topic_name as the Skill Name.

        return this.summaryRepository.save({
          interview_id: id,
          topic_name: skillName, // Saving 'HTML', 'CSS' etc.
          total_questions: stats.total,
          correct_count: stats.correct,
          score: parseFloat(((stats.correct / stats.total) * 100).toFixed(2)),
        });
      },
    );
    await Promise.all(summaryPromises);

    return await this.interviewRepository.findOne({ where: { id } });
  }

  private async generateAiFeedback(context: any): Promise<any> {
    console.log(
      '[DEBUG] Generating AI Feedback for Context:',
      JSON.stringify(context),
    );
    const {
      roleName,
      experienceLevel,
      totalScore,
      skillScores,
      weakTopics,
      strongTopics,
      detailedTopicStats,
      globalStats,
      terminationContext,
      topicAnalysis,
    } = context;

    const prompt = `
        You are an expert technical interviewer and hiring evaluator.
        Your task is to generate clear, professional, and explainable feedback based on a candidate’s AI interview performance.

        Context:
        - The interview was conducted using AI-generated MCQ questions.
        - The system is designed to eliminate manual screening and middlemen.
        - Feedback must help recruiters make fast, objective decisions.
        - Feedback must be fair, unbiased, and constructive.
        - This is an evaluation report, not encouragement or coaching.

        Job Role: ${roleName}
        Required Experience Level: ${experienceLevel}
        Overall Interview Score: ${totalScore}%

        IMPORTANT:
        The overall score is calculated against ALL scheduled questions.
        Unanswered questions due to timeout are treated as incorrect.

        Termination Status:
        ${JSON.stringify(terminationContext, null, 2)}
        
        Topic Analysis (Weakness Root Cause):
        ${JSON.stringify(topicAnalysis, null, 2)}

        Skill-wise Performance:
        ${JSON.stringify(skillScores, null, 2)}

        Detailed Topic Breakdown (Scheduled vs Attempted):
        ${JSON.stringify(detailedTopicStats, null, 2)}

        Global Stats:
        ${JSON.stringify(globalStats, null, 2)}

        Weak Topics (Low Accuracy Areas):
        ${weakTopics.length > 0 ? weakTopics.join(', ') : 'None'}

        Strong Topics:
        ${strongTopics.length > 0 ? strongTopics.join(', ') : 'None'}

        ------------------------------------
        CRITICAL EVALUATION RULES (MANDATORY)
        ------------------------------------

        1. IF termination_type is "timeout":
           - Treat the interview as PARTIALLY COMPLETED.
           - Do NOT praise time management.
           - Do NOT describe the overall score as fully representative.
           - Explicitly state that some topics were not evaluated.
           - Clarify that high accuracy applies ONLY to attempted questions.
           - Any unattempted topic MUST be treated as unassessed, not strong.
           - Hiring recommendation MUST be:
             "Conditional / Further Evaluation Required"

        2. Weak Topics Explanation Logic (Use 'Topic Analysis' data):
           - If reason is "unassessed_due_to_timeout":
             Explain that the topic was not covered due to time constraints (Coverage Gap).
           - If reason is "conceptual_gap":
             Explain that the candidate struggled with correctness in attempts (Skill Gap).
           - If reason is "none" or "strong", do NOT list it as a weakness.

        3. Strengths Identification:
           - Strengths MUST be based ONLY on topics that were attempted.
           - Do NOT infer strength for unattempted topics.
           - Do NOT exaggerate strengths based on partial coverage.

        4. IF Weak Topics is "None" OR Overall Score is 100% AND interview was fully completed:
           - Do NOT invent weaknesses.
           - Return empty arrays for:
             "weaknesses" and "improvement_suggestions"

        5. Language & Style Rules:
           - Do NOT mention exact questions, answers, or MCQ options.
           - Focus on conceptual understanding, not question performance.
           - Keep feedback concise, factual, and recruiter-focused.
           - Avoid emotional, motivational, or encouraging tone.

        ------------------------------------
        OUTPUT FORMAT (STRICT)
        ------------------------------------

        Return ONLY valid JSON.
        Do NOT include markdown, explanations, or extra text.

        {
          "overall_summary": "",
          "strengths": [],
          "weaknesses": [],
          "improvement_suggestions": [],
          "hiring_recommendation": "One of: 'Recommended', 'Not Recommended', 'Conditional / Further Evaluation Required'"
        }

        ------------------------------------
        HIRING RECOMMENDATION RULES
        ------------------------------------

        - Use "Recommended":
          Only if the interview was fully completed AND no critical weaknesses exist.

        - Use "Not Recommended":
          If there are clear conceptual gaps or consistently low accuracy.

        - Use "Conditional / Further Evaluation Required":
          IF the interview ended due to timeout OR if one or more topics were unassessed.
        `;

    try {
      const completion = await this.groq.chat.completions.create({
        messages: [
          {
            role: 'system',
            content:
              'You are a helpful assistant that outputs only valid JSON.',
          },
          { role: 'user', content: prompt },
        ],
        model: 'llama-3.1-8b-instant',
        temperature: 0.3,
      });

      const text = completion.choices[0]?.message?.content || '';
      const cleanText = text
        .replace(/```json/g, '')
        .replace(/```/g, '')
        .trim();
      return JSON.parse(cleanText);
    } catch (error) {
      console.error('AI Feedback Generation Failed:', error);
      return null; // Return null so we don't block completion
    }
  }
}
