import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In, ILike } from 'typeorm';
import {
  QuestionBank,
  QuestionType,
  Difficulty,
} from './entities/question_bank.entity';
import { GoogleGenerativeAI } from '@google/generative-ai';
import Groq from 'groq-sdk';
import { McqOptions } from '../mcq_options/entities/mcq_options.entity';
import { Skills } from '../skills/entities/skills.entity';

@Injectable()
export class QuestionBankService {
  private genAI: GoogleGenerativeAI;
  private model: any;
  private groq: Groq;

  constructor(
    @InjectRepository(QuestionBank)
    private questionBankRepository: Repository<QuestionBank>,
    @InjectRepository(McqOptions)
    private mcqOptionsRepository: Repository<McqOptions>,
    @InjectRepository(Skills)
    private skillsRepository: Repository<Skills>,
  ) {
    const apiKey = process.env.GROQ_API_KEY || '';
    if (apiKey) {
      // console.log(`Groq API Key found. Initializing Groq...`);
      this.groq = new Groq({ apiKey });
    }
  }

  async findByRole(
    roleId: number,
    page: number = 1,
    limit: number = 10,
  ): Promise<any> {
    const query = this.questionBankRepository
      .createQueryBuilder('question')
      .leftJoinAndSelect('question.options', 'options')
      .where('question.role_id = :roleId', { roleId })
      .andWhere('question.type = :type', { type: QuestionType.MOCK_TEST });

    // Get total count before pagination
    const total = await query.getCount();

    // Randomize order using RAND()
    const questions = await query
      .orderBy('RAND()')
      .skip((page - 1) * limit)
      .take(limit)
      .getMany();

    const allSkills = await this.skillsRepository.find();

    const mappedQuestions = questions.map((q) => {
      let skillName = 'General';
      if (q.primary_skill_id) {
        const skill = allSkills.find((s) => s.id === q.primary_skill_id);
        if (skill) skillName = skill.skill_name || 'Unknown';
      }

      // Shuffle options
      const shuffledOptions = this.shuffleArray(q.options || []);

      return {
        id: q.id,
        text: q.question_text,
        skill: skillName,
        difficulty: q.difficulty,
        role_id: q.role_id,
        options: shuffledOptions.map((opt) => ({
          id: opt.id,
          text: opt.option_text,
          isCorrect: opt.is_correct,
        })),
      };
    });

    return {
      data: mappedQuestions,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Fisher-Yates shuffle algorithm for array randomization
   */
  private shuffleArray<T>(array: T[]): T[] {
    const shuffled = [...array];
    for (let i = shuffled.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
    }
    return shuffled;
  }

  async getRandomQuestions(
    roleId: number,
    count: number,
    difficulty: string = 'medium',
  ): Promise<QuestionBank[]> {
    // Map string to Enum
    const diffEnum = Object.values(Difficulty).includes(
      difficulty as Difficulty,
    )
      ? (difficulty as Difficulty)
      : Difficulty.MEDIUM;

    const query = this.questionBankRepository
      .createQueryBuilder('qb')
      .leftJoinAndSelect('qb.options', 'options')
      .where('qb.role_id = :roleId', { roleId })
      .andWhere('qb.type = :type', { type: QuestionType.AI_TEST });

    if (difficulty) {
      query.andWhere('qb.difficulty = :difficulty', { difficulty: diffEnum });
    }

    // Randomize and limit
    // Note: RAND() is specific to MySQL. Use random() for Postgres if needed.
    return await query.orderBy('RAND()').take(count).getMany();
  }

  async getQuestionsByIds(ids: number[]): Promise<QuestionBank[]> {
    if (!ids || ids.length === 0) return [];
    return this.questionBankRepository.find({
      where: { id: In(ids) },
      relations: ['options'],
    });
  }

  /**
   * Checks if enough questions exist, if not, generates them via AI.
   */
  async ensureQuestionsExist(
    roleId: number,
    roleName: string,
    count: number,
    difficulty: string = 'medium',
    skills: any[] = [],
    experienceLevel: any = 'Entry Level',
  ) {
    const diffEnum = Object.values(Difficulty).includes(
      difficulty as Difficulty,
    )
      ? (difficulty as Difficulty)
      : Difficulty.MEDIUM;

    // We might want to include skills in the check, but for now strict count check is okay
    const existingCount = await this.questionBankRepository.count({
      where: {
        role_id: roleId,
        difficulty: diffEnum,
        type: QuestionType.AI_TEST,
      },
    });

    // Reuse only if we have a sufficiently large pool (e.g., 50+)
    // Otherwise, generate fresh questions to build up the DB
    if (existingCount >= 100) {
      return; // We have enough for random reuse
    }

    // Always generate full batch of NEW questions if under threshold
    // effectively ignoring 'needed' delta to ensure freshness for early candidates
    const questionsToGenerate = count;

    if (this.groq) {
      await this.generateAndSaveQuestions(
        roleId,
        roleName,
        questionsToGenerate,
        diffEnum,
        skills,
        experienceLevel,
      );
    } else {
      console.warn('Groq API Key missing, skipping generation.');
    }
  }

  private async generateAndSaveQuestions(
    roleId: number,
    roleName: string,
    count: number,
    difficulty: Difficulty,
    skills: any[],
    experienceLevel: any,
  ) {
    try {
      // Calculate distribution
      const validSkills =
        skills.length > 0 ? skills : [{ skill_name: roleName }];
      const totalWeight = validSkills.reduce(
        (sum, s) => sum + (s.count || 1),
        0,
      );
      let remainingQuestions = count;

      const skillDistribution = validSkills.map((skillObj, index) => {
        const weight = skillObj.count || 1;
        let qCount = Math.floor((weight / totalWeight) * count);

        if (totalWeight === count && skillObj.count) {
          qCount = skillObj.count;
        }

        if (index === validSkills.length - 1) {
          qCount = remainingQuestions;
        } else {
          remainingQuestions -= qCount;
        }

        return {
          skillName: skillObj.skill_name || skillObj.name,
          questionCount: Math.max(1, qCount),
        };
      });

      const prompt = `
          You are a professional technical interviewer and MCQ generator.
  
          Job Role:
          ${roleName}
  
          Required Experience Level:
          ${typeof experienceLevel === 'string' ? experienceLevel : 'Entry Level'}
  
          Difficulty Level:
          ${difficulty}
  
          Total Questions:
          ${count}
  
          Skills and Question Distribution:
          ${skillDistribution
            .map((s) => `- ${s.skillName}: ${s.questionCount} questions`)
            .join('\n')}
  
          Instructions:
          - Generate ONLY multiple-choice questions (MCQs)
          - Each question must have exactly 4 options
          - Only ONE option must be correct
          - Topics must be SPECIFIC and TECHNICAL
          - Language must be English
  
          Output Rules:
          - Return ONLY valid JSON
          - Each question object MUST contain:
            - "skill"
            - "topic"
            - "question_text"
            - "options" (array of 4 strings)
            - "correct_option_index" (0-3)

          Example Output:
          [
            {
              "skill": "HTML",
              "topic": "semantic elements",
              "question_text": "Which HTML tag is used for navigation?",
              "options": ["<nav>", "<section>", "<header>", "<aside>"],
              "correct_option_index": 0
            }
          ]
          `;

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

      let text = completion.choices[0]?.message?.content || '';
      text = text
        .replace(/```json/g, '')
        .replace(/```/g, '')
        .trim();

      let questionsData;
      try {
        questionsData = JSON.parse(text);
      } catch (e) {
        console.error('[DEBUG] JSON Parse Error:', e, 'Raw Text:', text);
        return;
      }

      if (!Array.isArray(questionsData)) {
        console.error('[DEBUG] Response is not an array:', questionsData);
        return;
      }

      for (const qData of questionsData) {
        let primarySkillId = null;
        if (qData.skill && skills.length > 0) {
          const matchedSkill = skills.find(
            (s) =>
              (s.skill_name &&
                s.skill_name.toLowerCase() === qData.skill.toLowerCase()) ||
              (s.name && s.name.toLowerCase() === qData.skill.toLowerCase()),
          );
          if (matchedSkill) primarySkillId = matchedSkill.id;
        }

        const q = this.questionBankRepository.create({
          role_id: roleId,
          question_text: qData.question_text,
          difficulty: difficulty,
          type: QuestionType.AI_TEST,
          ai_generated: true,
          created_at: new Date(),
          primary_skill_id: primarySkillId,
          topic_tags: qData.topic ? JSON.stringify([qData.topic]) : null,
          experience_level:
            typeof experienceLevel === 'string'
              ? experienceLevel
              : 'Entry Level',
        });
        const savedQ = await this.questionBankRepository.save(q);

        const options = qData.options.map((optText: string, idx: number) => {
          return this.mcqOptionsRepository.create({
            question_id: savedQ.id,
            option_text: optText,
            is_correct: idx === qData.correct_option_index,
          });
        });
        await this.mcqOptionsRepository.save(options);
      }
    } catch (error) {
      console.error('[DEBUG] AI Generation Failed:', error);
    }
  }

  public async generateAndSaveMockTestQuestions(
    roleId: number,
    roleName: string,
    count: number,
    difficulty: Difficulty,
    skills: any[],
    experienceLevel: any,
  ) {
    const normalizedSkills = skills.map((s) => {
      if (typeof s === 'string') {
        return { skill_name: s, count: 1 };
      }
      return s;
    });

    // console.log(`[DEBUG] generateAndSaveMockTestQuestions called at ${new Date().toISOString()}. Requested count: ${count}`);

    try {
      const validSkills =
        normalizedSkills.length > 0
          ? normalizedSkills
          : [{ skill_name: roleName }];
      const totalWeight = validSkills.reduce(
        (sum, s) => sum + (s.count || 1),
        0,
      );
      let remainingQuestions = count;

      const skillDistribution = validSkills.map((skillObj, index) => {
        const weight = skillObj.count || 1;
        let qCount = Math.floor((weight / totalWeight) * count);

        if (totalWeight === count && skillObj.count) {
          qCount = skillObj.count;
        }

        if (index === validSkills.length - 1) {
          qCount = remainingQuestions;
        } else {
          remainingQuestions -= qCount;
        }

        return {
          skillName: skillObj.skill_name || skillObj.name,
          questionCount: Math.max(1, qCount),
        };
      });

      const prompt = `
          You are a professional technical interviewer and MCQ generator.
  
          Job Role: ${roleName}
          Experience: ${typeof experienceLevel === 'string' ? experienceLevel : 'Entry Level'}
          Difficulty: ${difficulty}
          Total Questions: ${count}
  
          Skills:
          ${skillDistribution.map((s) => `- ${s.skillName}: ${s.questionCount}`).join('\n')}
  
          Instructions:
          - Generate ONLY multiple-choice questions (MCQs)
          - 4 options per question, 1 correct
          - Specific topics
          - Return valid JSON
  
          Output Format:
          [
            {
              "skill": "Skill Name",
              "topic": "Topic",
              "question_text": "Question?",
              "options": ["A", "B", "C", "D"],
              "correct_option_index": 0
            }
          ]
          `;

      if (!this.groq) {
        console.warn('Groq API Key missing, skipping generation.');
        return { success: false, error: 'Groq API Key missing' };
      }

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

      let text = completion.choices[0]?.message?.content || '';
      text = text
        .replace(/```json/g, '')
        .replace(/```/g, '')
        .trim();

      let questionsData;
      try {
        questionsData = JSON.parse(text);
      } catch (e) {
        return { success: false, error: 'JSON Parse Error' };
      }

      if (!Array.isArray(questionsData)) {
        return { success: false, error: 'Response is not an array' };
      }

      // console.log(`[DEBUG] AI generated ${questionsData.length} questions. Requested: ${count}`);

      // Safeguard: Ensure we don't save more than requested (handling AI over-generation)
      if (questionsData.length > count) {
        questionsData = questionsData.slice(0, count);
      }

      const savedQuestionsWithDetails: any[] = [];
      const allSkills = await this.skillsRepository.find();

      for (const qData of questionsData) {
        let primarySkillId = null;
        if (qData.skill) {
          const matchedSkill = allSkills.find(
            (s) => s.skill_name.toLowerCase() === qData.skill.toLowerCase(),
          );
          if (matchedSkill) {
            primarySkillId = matchedSkill.id;
          }
        }

        const q = this.questionBankRepository.create({
          role_id: roleId,
          question_text: qData.question_text,
          difficulty: difficulty,
          type: QuestionType.MOCK_TEST,
          ai_generated: true,
          created_at: new Date(),
          primary_skill_id: primarySkillId,
          topic_tags: qData.topic ? JSON.stringify([qData.topic]) : null,
          experience_level:
            typeof experienceLevel === 'string'
              ? experienceLevel
              : 'Entry Level',
        });
        const savedQ = await this.questionBankRepository.save(q);

        const options = qData.options.map((optText: string, idx: number) => {
          return this.mcqOptionsRepository.create({
            question_id: savedQ.id,
            option_text: optText,
            is_correct: idx === qData.correct_option_index,
          });
        });
        const savedOptions = await this.mcqOptionsRepository.save(options);

        // Map to frontend expected format
        savedQuestionsWithDetails.push({
          id: savedQ.id,
          text: savedQ.question_text,
          skill: qData.skill,
          difficulty: savedQ.difficulty,
          role: roleName,
          options: savedOptions.map((opt) => ({
            id: opt.id,
            text: opt.option_text,
            isCorrect: opt.is_correct,
          })),
        });
      }
      // console.log(`[DEBUG] Successfully saved ${savedQuestionsWithDetails.length} questions.`);
      return {
        success: true,
        count: savedQuestionsWithDetails.length,
        data: savedQuestionsWithDetails,
      };
    } catch (error) {
      console.error('[DEBUG] AI Mock Generation Failed:', error);
      return { success: false, error: error.message };
    }
  }

  async updateQuestion(id: number, data: any) {
    const question = await this.questionBankRepository.findOne({
      where: { id },
      relations: ['options'],
    });
    if (!question) {
      throw new Error('Question not found');
    }

    if (data.text) question.question_text = data.text;
    if (data.difficulty) question.difficulty = data.difficulty;

    if (data.skill) {
      const skillEntity = await this.skillsRepository.findOne({
        where: { skill_name: data.skill },
      });
      if (skillEntity) {
        question.primary_skill_id = skillEntity.id;
      }
    }

    await this.questionBankRepository.save(question);

    if (data.options && Array.isArray(data.options)) {
      await this.mcqOptionsRepository.delete({ question_id: id });
      const newOptions = data.options.map((opt: any) => {
        return this.mcqOptionsRepository.create({
          question_id: id,
          option_text: opt.text,
          is_correct: opt.isCorrect,
        });
      });
      await this.mcqOptionsRepository.save(newOptions);
    }

    const updatedQ = await this.questionBankRepository.findOne({
      where: { id },
      relations: ['options'],
    });

    // Map response for frontend
    return {
      id: updatedQ.id,
      text: updatedQ.question_text,
      skill: data.skill || 'Unknown', // we don't store skill name directly in entity, so rely on input or separate lookup
      difficulty: updatedQ.difficulty,
      options: updatedQ.options.map((opt) => ({
        id: opt.id,
        text: opt.option_text,
        isCorrect: opt.is_correct,
      })),
    };
  }

  async deleteQuestion(id: number) {
    const question = await this.questionBankRepository.findOne({
      where: { id },
    });
    if (!question) {
      throw new Error('Question not found');
    }
    await this.mcqOptionsRepository.delete({ question_id: id });
    return this.questionBankRepository.delete(id);
  }
}
