import {
  Injectable,
  BadRequestException,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AiInterviewSchedules } from './entities/ai_interview_schedules.entity';
import { EmailTokensService } from '../email_tokens/email_tokens.service';
import { JobPostAiSettings } from '../job_post_ai_settings/entities/job_post_ai_settings.entity';
import { QuestionBankService } from '../question_bank/question_bank.service';
import { JobPosts } from '../job_posts/entities/job_posts.entity';
import { Roles } from '../roles/entities/roles.entity';
import * as crypto from 'crypto';
import * as moment from 'moment'; // Assuming moment is available, or use native Date
import { UserAiInterviewsService } from '../user_ai_interviews/user_ai_interviews.service';
import { SeekerProfiles } from '../seeker_profiles/entities/seeker_profiles.entity';
import { JobPostSkills } from '../job_post_skills/entities/job_post_skills.entity';
import { Skills } from '../skills/entities/skills.entity';
import { Experience } from '../experience/entities/experience.entity';

@Injectable()
export class AiInterviewSchedulesService {
  constructor(
    @InjectRepository(AiInterviewSchedules)
    private readonly scheduleRepository: Repository<AiInterviewSchedules>,
    @InjectRepository(JobPostAiSettings)
    private readonly jobPostAiSettingsRepository: Repository<JobPostAiSettings>,
    @InjectRepository(JobPosts)
    private readonly jobPostsRepository: Repository<JobPosts>,
    @InjectRepository(Roles)
    private readonly rolesRepository: Repository<Roles>,
    @InjectRepository(SeekerProfiles)
    private readonly seekerProfilesRepository: Repository<SeekerProfiles>,
    @InjectRepository(JobPostSkills)
    private readonly jobPostSkillsRepository: Repository<JobPostSkills>,
    @InjectRepository(Skills)
    private readonly skillsRepository: Repository<Skills>,
    @InjectRepository(Experience)
    private readonly experienceRepository: Repository<Experience>,
    private readonly questionBankService: QuestionBankService,
    private readonly emailTokensService: EmailTokensService,
    private readonly userAiInterviewsService: UserAiInterviewsService,
  ) {}

  /**
   * Create schedule (instant or scheduled)
   */
  async createSchedule(data: {
    application_id: number;
    seeker_id: number;
    job_id: number;
    requester_user_id: number;
    scheduled_at?: Date;
    mode?: 'instant' | 'scheduled';
  }) {
    // Fetch AI Settings
    const aiSettings = await this.jobPostAiSettingsRepository.findOne({
      where: { job_post_id: data.job_id },
    });

    const jobPost = await this.jobPostsRepository.findOne({
      where: { id: data.job_id },
    });

    if (!aiSettings || !aiSettings.ai_enabled) {
      throw new BadRequestException('AI Interview is not enabled for this job');
    }

    // Check for existing active schedule
    const existingSchedule = await this.scheduleRepository.findOne({
      where: {
        application_id: data.application_id,
        seeker_id: data.seeker_id,
      },
      order: { created_at: 'DESC' },
    });

    if (existingSchedule) {
      const terminalStatuses = [
        'completed',
        'rejected',
        'expired',
        'cancelled',
        'COMPLETED',
        'NO_SHOW',
      ];
      if (!terminalStatuses.includes(existingSchedule.status)) {
        return existingSchedule;
      }
    }

    let scheduleStatus = 'requested';
    const mode = data.mode || (data.scheduled_at ? 'scheduled' : 'instant');

    if (mode === 'instant') {
      // Validate Time Window
      const now = new Date();
      const currentTime = now.toTimeString().split(' ')[0]; // HH:MM:SS
      const startTime = aiSettings.ai_start_time;
      const endTime = aiSettings.ai_end_time;

      if (startTime && endTime) {
        if (currentTime < startTime || currentTime > endTime) {
          throw new BadRequestException(
            `Instant interviews are only available between ${startTime} and ${endTime}`,
          );
        }
      }
      scheduleStatus = 'approved'; // Instant is auto-approved/ready (or 'requested' -> 'IN_PROGRESS' immediately?)
      // Let's keep it 'requested' or 'approved' and client transitions to 'IN_PROGRESS'
    } else if (mode === 'scheduled') {
      if (data.scheduled_at) {
        // Validate Future Date
        if (
          new Date(data.scheduled_at) <= new Date(Date.now() - 5 * 60 * 1000)
        ) {
          throw new BadRequestException('Scheduled time must be in the future');
        }

        // Validate Job Expiry
        if (jobPost && jobPost.expires_at) {
          if (new Date(data.scheduled_at) > new Date(jobPost.expires_at)) {
            throw new BadRequestException(
              'Scheduled date cannot be after the job expiry date',
            );
          }
        }

        // Validate Validity Days
        if (aiSettings && aiSettings.ai_validity_days) {
          const now = new Date();
          const validityDate = new Date(now);
          validityDate.setDate(
            validityDate.getDate() + aiSettings.ai_validity_days,
          );
          if (new Date(data.scheduled_at) > validityDate) {
            throw new BadRequestException(
              `Interview must be completed within ${aiSettings.ai_validity_days} days of application`,
            );
          }
        }

        scheduleStatus = 'scheduled';
      }
    }

    // Generate and Lock Questions
    let questionIds: number[] = [];
    if (jobPost) {
      questionIds = await this.generateQuestionsForJob(jobPost, aiSettings);
    }

    const schedule = this.scheduleRepository.create({
      ...data,
      mode: mode,
      status: scheduleStatus,
      questions_list: questionIds,
      schedule_token: crypto.randomBytes(32).toString('hex'), // Always generate token for both
    });

    const savedSchedule = await this.scheduleRepository.save(schedule);

    // Create email token if scheduled
    if (data.scheduled_at && savedSchedule.schedule_token) {
      await this.emailTokensService.createToken({
        token: savedSchedule.schedule_token,
        token_type: 'ai_interview_schedule',
        reference_id: savedSchedule.id,
        user_id: data.seeker_id,
        expires_at: new Date(
          new Date(data.scheduled_at).getTime() + 24 * 60 * 60 * 1000,
        ), // 24 hours after scheduled time
      });
    }

    return savedSchedule;
  }

  /**
   * Helper to generate/fetch questions for a job
   */
  private async generateQuestionsForJob(
    jobPost: JobPosts,
    aiSettings: JobPostAiSettings,
  ): Promise<number[]> {
    if (!jobPost || !jobPost.role_id) return [];

    const role = await this.rolesRepository.findOne({
      where: { id: jobPost.role_id },
    });
    // console.log(`[DEBUG] generateQuestionsForJob - Role: ${role?.name}, Count: ${aiSettings?.ai_question_count}`);

    if (role && aiSettings) {
      let jobSkills: any[] = [];

      // PRIORITY 1: Check ai_config in settings
      if (aiSettings.ai_config) {
        try {
          const config =
            typeof aiSettings.ai_config === 'string'
              ? JSON.parse(aiSettings.ai_config)
              : aiSettings.ai_config;

          if (Array.isArray(config) && config.length > 0) {
            // console.log(`[DEBUG] Found ai_config skills: ${JSON.stringify(config)}`);
            // config format: [{ skill: "React.js", count: 20 }]
            const configSkills = config.map((c) => c.skill || c.skill_name);

            // Fetch IDs for these skills from DB
            if (configSkills.length > 0) {
              const dbSkills = await this.skillsRepository
                .createQueryBuilder('skill')
                .where('skill.skill_name IN (:...names)', {
                  names: configSkills,
                })
                .getMany();

              // Map config to structure with IDs
              jobSkills = config.map((c) => {
                const skillName = c.skill || c.skill_name;
                const dbSkill = dbSkills.find(
                  (s) => s.skill_name.toLowerCase() === skillName.toLowerCase(),
                );
                return {
                  id: dbSkill ? dbSkill.id : null,
                  skill_name: skillName,
                  count: c.count,
                };
              });
            }
          }
        } catch (e) {
          console.error('[DEBUG] Failed to parse ai_config', e);
        }
      }

      // PRIORITY 2: Fallback to Job Post Skills table if no config
      if (jobSkills.length === 0) {
        jobSkills = await this.jobPostSkillsRepository
          .createQueryBuilder('jps')
          .leftJoin('skills', 's', 's.id = jps.skill_id')
          .select(['s.id AS id', 's.skill_name AS skill_name'])
          .where('jps.job_post_id = :jobId', { jobId: jobPost.id })
          .getRawMany();
        // console.log(`[DEBUG] Fetched DB Job Skills: ${JSON.stringify(jobSkills)}`);
      } else {
        // console.log(`[DEBUG] Using Config Job Skills: ${JSON.stringify(jobSkills)}`);
      }

      // Fetch Job Experience with explicit JOIN
      let experienceLevel: any = aiSettings.ai_experience_level;
      if (jobPost.experience_id) {
        const experienceData = await this.jobPostsRepository
          .createQueryBuilder('job')
          .leftJoin('experiences', 'exp', 'exp.id = job.experience_id')
          .select(['exp.level AS level', 'exp.duration AS duration'])
          .where('job.id = :id', { id: jobPost.id })
          .getRawOne();

        if (experienceData) {
          experienceLevel = `${experienceData.level} (${experienceData.duration})`;
        }
      }

      await this.questionBankService.ensureQuestionsExist(
        jobPost.role_id,
        role.name,
        aiSettings.ai_question_count || 5,
        aiSettings.ai_difficulty,
        jobSkills,
        experienceLevel,
      );

      const questions = await this.questionBankService.getRandomQuestions(
        jobPost.role_id,
        aiSettings.ai_question_count || 5,
        aiSettings.ai_difficulty,
      );
      return questions.map((q) => q.id);
    }
    return [];
  }

  /**
   * Get schedule by token
   */
  async getScheduleByToken(token: string) {
    const schedule = await this.scheduleRepository.findOne({
      where: { schedule_token: token },
    });

    if (!schedule) return null;

    const aiSettings = await this.jobPostAiSettingsRepository.findOne({
      where: { job_post_id: schedule.job_id },
    });

    // SELF-HEALING: If no questions, try to backfill
    if (
      (!schedule.questions_list || schedule.questions_list.length === 0) &&
      aiSettings
    ) {
      // console.log(`[DEBUG] Schedule ${schedule.id} has no questions. Attempting to backfill...`);
      const jobPost = await this.jobPostsRepository.findOne({
        where: { id: schedule.job_id },
      });
      if (jobPost) {
        const newQuestionIds = await this.generateQuestionsForJob(
          jobPost,
          aiSettings,
        );
        if (newQuestionIds.length > 0) {
          schedule.questions_list = newQuestionIds;
          await this.scheduleRepository.save(schedule);
          // console.log(`[DEBUG] Backfilled ${newQuestionIds.length} questions for Schedule ${schedule.id}`);
        }
      }
    }

    const response: any = { ...schedule };

    // Fetch Job and Company Details via QueryBuilder (no relation decorator used on entity)
    const jobData = await this.jobPostsRepository
      .createQueryBuilder('job')
      .leftJoin('companies', 'company', 'company.id = job.company_id')
      .select(['job.title', 'company.name', 'company.logo'])
      .where('job.id = :id', { id: schedule.job_id })
      .getRawOne();

    if (jobData) {
      response.company = {
        name: jobData.company_name, // Aliased by TypeORM as table_column
        logo: jobData.company_logo,
      };
      response.job_title = jobData.job_title;
    }

    if (aiSettings) {
      response.ai_settings = {
        duration_minutes: aiSettings.ai_duration_minutes,
        question_count: aiSettings.ai_question_count,
      };
    }

    if (schedule.questions_list && schedule.questions_list.length > 0) {
      const questions = await this.questionBankService.getQuestionsByIds(
        schedule.questions_list,
      );
      response.questions = questions;
    }

    if (schedule.status === 'completed' && schedule.interview_id) {
      const interview = await this.userAiInterviewsService.findOne(schedule.interview_id);
      if (interview) {
        response.score = interview.score;
        response.ai_feedback_json = interview.ai_feedback_json;
      }
    }

    return response;
  }

  /**
   * Validate ownership
   */
  async validateOwnership(
    scheduleId: number,
    seekerId: number,
  ): Promise<boolean> {
    const schedule = await this.scheduleRepository.findOne({
      where: { id: scheduleId },
    });

    return schedule && schedule.seeker_id === seekerId;
  }

  /**
   * Start interview
   */
  async startInterview(scheduleId: number) {
    const schedule = await this.scheduleRepository.findOne({
      where: { id: scheduleId },
    });
    if (!schedule) throw new Error('Schedule not found');

    // Check for existing active interview to prevent duplicates
    const existingInterview =
      await this.userAiInterviewsService.findActiveBySchedule(schedule.id);
    if (existingInterview) {
      // console.log(`[DEBUG] Resuming existing interview ${existingInterview.id} for schedule ${scheduleId}`);
      return { ...schedule, interview_id: existingInterview.id };
    }

    // Update schedule status
    schedule.status = 'in_progress';
    schedule.started_at = new Date();
    await this.scheduleRepository.save(schedule);

    // Resolve User ID from Seeker ID (Profile ID)
    const seekerProfile = await this.seekerProfilesRepository.findOne({
      where: { id: schedule.seeker_id },
    });
    if (!seekerProfile) {
      throw new Error(`Seeker profile not found for id ${schedule.seeker_id}`);
    }

    // Create UserAiInterviews record using the mapped user_id
    const interview = await this.userAiInterviewsService.create({
      user_id: seekerProfile.user_id, // Map profile ID to master User ID
      job_post_id: schedule.job_id,
      application_id: schedule.application_id,
      schedule_id: schedule.id,
      interview_mode: schedule.mode,
      total_questions: schedule.questions_list
        ? schedule.questions_list.length
        : 0,
      status: 'in_progress',
      started_at: new Date(),
    });

    // Return schedule but include interview_id if needed by frontend
    return { ...schedule, interview_id: interview.id };
  }

  /**
   * Complete interview
   */
  async completeInterview(
    scheduleId: number,
    data?: { submission_type?: string; reason?: string },
  ) {
    // Complete the UserAiInterviews session
    const interview =
      await this.userAiInterviewsService.findActiveBySchedule(scheduleId);
    let completedInterview = null;
    if (interview) {
      completedInterview = await this.userAiInterviewsService.complete(interview.id);
    }

    const updateData: any = {
      status: 'completed',
      completed_at: new Date(),
    };

    if (data && (data.submission_type || data.reason)) {
      updateData.submission_meta = {
        type: data.submission_type,
        reason: data.reason,
        timestamp: new Date(),
      };
    }

    // Update Schedule Status
    await this.scheduleRepository.update(scheduleId, updateData);

    const schedule = await this.scheduleRepository.findOne({ where: { id: scheduleId } });
    if (schedule) {
      const finalInterview = completedInterview || (schedule.interview_id ? await this.userAiInterviewsService.findOne(schedule.interview_id) : null);
      return {
        ...schedule,
        score: finalInterview ? finalInterview.score : null,
        ai_feedback_json: finalInterview ? finalInterview.ai_feedback_json : null,
      };
    }
    return schedule;
  }

  /**
   * Mark as NO_SHOW
   */
  async markNoShow(scheduleId: number) {
    await this.scheduleRepository.update(scheduleId, {
      status: 'no_show',
    });

    return { success: true };
  }

  /**
   * Submit answer
   */
  async submitAnswer(id: number, questionId: number, answer: any) {
    const schedule = await this.scheduleRepository.findOne({ where: { id } });
    if (!schedule) throw new Error('Schedule not found');

    const interview =
      await this.userAiInterviewsService.findActiveBySchedule(id);
    if (!interview) {
      throw new Error('No active interview session found');
    }

    // Fetch Question to Verify Answer
    // We use QuestionBankService to get question + options
    const questions = await this.questionBankService.getQuestionsByIds([
      questionId,
    ]);
    const question = questions[0];

    let isCorrect = false;
    let answerText = '';
    let chosenOptionIds = null;

    // console.log(`[DEBUG] Submitting Answer - ScheduleID: ${id}, QuestionID: ${questionId}`);
    // console.log(`[DEBUG] Received Answer Payload:`, answer);

    if (question) {
      // console.log(`[DEBUG] Question Found: ${question.id}, Options Count: ${question.options?.length}`);
      if (question.options) {
        // Assuming 'answer' is selectedOptionId (number)
        const selectedOptionId = Number(answer?.answer || answer); // Handle object {answer: id} or direct id

        // console.log(`[DEBUG] Parsed SelectedOptionId: ${selectedOptionId}`);

        // Find selected option text for logging/backup
        const selectedOption = question.options.find(
          (o) => o.id === selectedOptionId,
        );

        if (selectedOption) {
          // console.log(`[DEBUG] Option Matched: ${selectedOption.option_text}, IsCorrect: ${selectedOption.is_correct}`);
          answerText = selectedOption.option_text;
          chosenOptionIds = [selectedOptionId]; // Pass array directly for JSON column
          isCorrect = selectedOption.is_correct;
        } else {
          // console.log(`[DEBUG] No Option Matched for ID: ${selectedOptionId}`);
        }
      } else {
        // console.log(`[DEBUG] Question has no options loaded`);
      }
    } else {
      // console.log(`[DEBUG] Question not found in DB`);
    }

    // Save answer
    await this.userAiInterviewsService.saveAnswer({
      interview_id: interview.id,
      question_id: questionId,
      answer_text: answerText,
      chosen_option_ids: chosenOptionIds,
      is_correct: isCorrect,
      score: isCorrect ? 1 : 0, // Assign score 1 for correct, 0 for incorrect
      answered_at: new Date(),
    });

    // Also update legacy column for backup
    let currentAnswers = schedule.answers || {};
    currentAnswers[questionId] = answer;
    schedule.answers = currentAnswers;
    return await this.scheduleRepository.save(schedule);
  }

  /**
   * Bulk Submit Answers
   */
  async submitBulkAnswers(
    id: number,
    answers: { questionId: number; answer: any }[],
  ) {
    const schedule = await this.scheduleRepository.findOne({ where: { id } });
    if (!schedule) throw new Error('Schedule not found');

    const interview =
      await this.userAiInterviewsService.findActiveBySchedule(id);
    if (!interview) {
      throw new Error('No active interview session found');
    }

    // Fetch all questions involved
    const questionIds = answers.map((a) => a.questionId);
    const questions =
      await this.questionBankService.getQuestionsByIds(questionIds);
    const questionsMap = new Map(questions.map((q) => [q.id, q]));

    let currentAnswers = schedule.answers || {};
    const results = [];

    for (const ansItem of answers) {
      const { questionId, answer } = ansItem;
      const question = questionsMap.get(questionId);

      let isCorrect = false;
      let answerText = '';
      let chosenOptionIds = null;

      if (question && question.options) {
        const selectedOptionId = Number(answer?.answer || answer);
        const selectedOption = question.options.find(
          (o) => o.id === selectedOptionId,
        );

        if (selectedOption) {
          answerText = selectedOption.option_text;
          chosenOptionIds = [selectedOptionId];
          isCorrect = selectedOption.is_correct;
        }
      }

      // Save individual answer record
      await this.userAiInterviewsService.saveAnswer({
        interview_id: interview.id,
        question_id: questionId,
        answer_text: answerText,
        chosen_option_ids: chosenOptionIds,
        is_correct: isCorrect,
        score: isCorrect ? 1 : 0,
        answered_at: new Date(),
      });

      // Update legacy map
      currentAnswers[questionId] = answer;
      results.push({ questionId, status: 'saved' });
    }

    // Save schedule once
    schedule.answers = currentAnswers;
    await this.scheduleRepository.save(schedule);

    return { success: true, processed: results.length };
  }

  /**
   * Get seeker's schedules with job details
   */
  async getSeekerSchedules(seekerId: number) {
    return await this.scheduleRepository
      .createQueryBuilder('schedule')
      .leftJoin('job_posts', 'job', 'job.id = schedule.job_id')
      .leftJoin('companies', 'company', 'company.id = job.company_id')
      .leftJoin(
        'job_post_ai_settings',
        'ai_settings',
        'ai_settings.job_post_id = job.id',
      )
      .leftJoin('user_ai_interviews', 'interview', 'interview.id = schedule.interview_id')
      .select([
        'schedule.id AS id',
        'schedule.application_id AS application_id',
        'schedule.seeker_id AS seeker_id',
        'schedule.job_id AS job_id',
        'schedule.scheduled_at AS scheduled_at',
        'schedule.status AS status',
        'schedule.schedule_token AS schedule_token',
        'schedule.started_at AS started_at',
        'schedule.completed_at AS completed_at',
        'schedule.created_at AS created_at',
        'schedule.updated_at AS updated_at',
        'job.title AS job_title',
        'ai_settings.ai_duration_minutes AS duration',
        'company.name AS company_name',
        'company.logo AS company_logo',
        'interview.score AS score',
      ])
      .where('schedule.seeker_id = :seekerId', { seekerId })
      .orderBy('schedule.created_at', 'DESC')
      .getRawMany();
  }

  /**
   * Get job's schedules
   */
  async getJobSchedules(jobId: number) {
    return await this.scheduleRepository.find({
      where: { job_id: jobId },
      order: { created_at: 'DESC' },
    });
  }

  /**
   * Update schedule (for rescheduling)
   */
  async updateSchedule(scheduleId: number, data: { scheduled_at: Date }) {
    const scheduledDate = new Date(data.scheduled_at);
    // Allow scheduling up to 5 minutes in the past to account for clock drift
    if (scheduledDate <= new Date(Date.now() - 5 * 60 * 1000)) {
      throw new BadRequestException('Scheduled time must be in the future');
    }

    const schedule = await this.scheduleRepository.findOne({
      where: { id: scheduleId },
    });
    if (!schedule) throw new NotFoundException('Schedule not found');

    // Fetch Job and Settings for validation
    const job = await this.jobPostsRepository.findOne({
      where: { id: schedule.job_id },
    });
    if (job && job.expires_at) {
      if (new Date(data.scheduled_at) > new Date(job.expires_at)) {
        throw new BadRequestException(
          'Scheduled date cannot be after the job expiry date',
        );
      }
    }

    const aiSettings = await this.jobPostAiSettingsRepository.findOne({
      where: { job_post_id: schedule.job_id },
    });
    if (aiSettings && aiSettings.ai_validity_days) {
      const validityDate = new Date(schedule.created_at);
      validityDate.setDate(
        validityDate.getDate() + aiSettings.ai_validity_days,
      );
      if (new Date(data.scheduled_at) > validityDate) {
        throw new BadRequestException(
          `Interview must be completed within ${aiSettings.ai_validity_days} days of application`,
        );
      }
    }

    await this.scheduleRepository.update(scheduleId, {
      scheduled_at: data.scheduled_at,
      reminder_1day_sent: false,
      reminder_1hour_sent: false,
      status: 'scheduled', // Ensure status is set back to scheduled
    });

    // Update email token expiry if exists?
    // Ideally we update the token too, but for now validation is key.

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

  /**
   * Get schedule by ID
   */
  async getScheduleById(id: number) {
    const schedule = await this.scheduleRepository.findOne({ where: { id } });
    if (!schedule) return null;

    const response: any = { ...schedule };

    // Fetch Job Details for Expiry
    const jobPost = await this.jobPostsRepository.findOne({
      where: { id: schedule.job_id },
      select: ['expires_at'],
    });

    if (jobPost) {
      response.job_expires_at = jobPost.expires_at;
    }

    // Fetch AI Settings
    const aiSettings = await this.jobPostAiSettingsRepository.findOne({
      where: { job_post_id: schedule.job_id },
    });

    if (aiSettings) {
      response.ai_settings = {
        duration_minutes: aiSettings.ai_duration_minutes,
        question_count: aiSettings.ai_question_count,
        validity_days: aiSettings.ai_validity_days,
        start_time: aiSettings.ai_start_time,
        end_time: aiSettings.ai_end_time,
      };
    }
    return response;
  }
}
