import { Injectable } from '@nestjs/common';
import { DataSource, QueryRunner } from 'typeorm';
import { TemplateQuestionRepository } from './template-question.repository';
import { CreateTemplateQuestionDto } from './dto/create-template-question.dto';
import { UpdateTemplateQuestionDto } from './dto/update-template-question.dto';
import { FilterTemplateQuestionDto } from './dto/filter-template-question.dto';
import { CloneQuestionsDto } from './dto/clone-questions.dto';
import { InifniNotFoundException } from 'src/common/exceptions/infini-notfound-exception';
import InifniBadRequestException from 'src/common/exceptions/infini-badrequest-exception';
import { InifniInternalServerErrorException } from 'src/common/exceptions/infini-internalservererror-exception';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { AppLoggerService } from 'src/common/services/logger.service';
import { MasterQuestionRepository } from 'src/master-question/master-question.repository';
import { TemplateFormSectionRepository } from 'src/template-form-section/template-form-section.repository';
import { TemplateQuestion } from 'src/common/entities/template-question.entity';
import { handleKnownErrors } from 'src/common/utils/handle-error.util';

@Injectable()
export class TemplateQuestionService {
  constructor(
    private readonly templateQuestionRepo: TemplateQuestionRepository,
    private readonly masterQuestionRepo: MasterQuestionRepository,
    private readonly templateFormSectionRepo: TemplateFormSectionRepository,
    private readonly logger: AppLoggerService,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Get all template questions with filters
   */
  async findAll(filters: FilterTemplateQuestionDto) {
    try {
      this.logger.log('Finding all template questions', { filters });
      return await this.templateQuestionRepo.findAll(filters);
    } catch (error) {
      this.logger.error('Error finding template questions', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_FIND_FAILED, error);
    }
  }

  /**
   * Get a single template question by ID
   */
  async findById(id: number) {
    try {
      this.logger.log('Finding template question by ID', { id });
      const question = await this.templateQuestionRepo.findById(id);
      
      if (!question) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_QUESTION_NOT_FOUND,
          new Error(`Template question not found: ${id}`)
        );
      }

      return question;
    } catch (error) {
      this.logger.error('Error finding template question by ID', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_FIND_BY_ID_FAILED, error);
    }
  }

  /**
   * Get questions by template section
   */
  async findByTemplateSection(templateFormSectionId: number) {
    try {
      this.logger.log('Finding questions by template section', { templateFormSectionId });
      
      // Verify section exists
      const section = await this.templateFormSectionRepo.findById(templateFormSectionId);
      if (!section) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
          new Error(`Template form section not found: ${templateFormSectionId}`)
        );
      }

      return await this.templateQuestionRepo.findByTemplateSection(templateFormSectionId);
    } catch (error) {
      this.logger.error('Error finding questions by template section', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_FIND_BY_SECTION_FAILED, error);
    }
  }

  /**
   * Get questions by program template
   */
  async findByProgramTemplate(programTemplateId: number) {
    try {
      this.logger.log('Finding questions by program template', { programTemplateId });
      return await this.templateQuestionRepo.findByProgramTemplate(programTemplateId);
    } catch (error) {
      this.logger.error('Error finding questions by program template', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_FIND_FAILED, error);
    }
  }

  /**
   * Create a template question
   */
  async create(createDto: CreateTemplateQuestionDto) {
    try {
      this.logger.log('Creating template question', { createDto });

      // Verify template section exists
      const section = await this.templateFormSectionRepo.findById(createDto.templateFormSectionId);
      if (!section) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
          new Error(`Template form section not found: ${createDto.templateFormSectionId}`)
        );
      }

      // Check if question code already exists in this template
      const existingQuestion = await this.templateQuestionRepo.findByQuestionCodeAndTemplate(
        createDto.questionCode,
        section.programTemplateId,
      );

      if (existingQuestion) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_QUESTION_DUPLICATE_CODE,
          new Error(`Duplicate question code: ${createDto.questionCode}`)
        );
      }

      // Auto-assign display order if not provided
      if (!createDto.displayOrder) {
        createDto.displayOrder = await this.templateQuestionRepo.getNextDisplayOrder(
          createDto.templateFormSectionId,
        );
      }

      const question = await this.templateQuestionRepo.create(createDto);
      this.logger.log('Template question created successfully', { questionId: question.id });
      
      return question;
    } catch (error) {
      this.logger.error('Error creating template question', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_CREATE_FAILED, error);
    }
  }

  /**
   * Update a template question
   */
  async update(id: number, updateDto: UpdateTemplateQuestionDto) {
    try {
      this.logger.log('Updating template question', { id, updateDto });

      // Verify question exists
      const existingQuestion = await this.findById(id);

      // If updating question code, check uniqueness
      if (updateDto.questionCode) {
        const section = await this.templateFormSectionRepo.findById(existingQuestion.templateFormSectionId);
        if (!section) {
          handleKnownErrors(
            ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
            new Error(`Template form section not found: ${existingQuestion.templateFormSectionId}`)
          );
        }
        const duplicateQuestion = await this.templateQuestionRepo.findByQuestionCodeAndTemplate(
          updateDto.questionCode,
          section.programTemplateId,
        );

        if (duplicateQuestion && duplicateQuestion.id !== id) {
          handleKnownErrors(
            ERROR_CODES.TEMPLATE_QUESTION_DUPLICATE_CODE,
            new Error(`Duplicate question code: ${updateDto.questionCode}`)
          );
        }
      }

      const updated = await this.templateQuestionRepo.update(id, updateDto);
      this.logger.log('Template question updated successfully', { questionId: id });
      
      return updated;
    } catch (error) {
      this.logger.error('Error updating template question', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_UPDATE_FAILED, error);
    }
  }

  /**
   * Delete a template question
   */
  async delete(id: number, userId?: number) {
    try {
      this.logger.log('Deleting template question', { id, userId });

      // Verify question exists
      await this.findById(id);

      await this.templateQuestionRepo.softDelete(id, userId);
      this.logger.log('Template question deleted successfully', { questionId: id });
      
      return { deleted: true };
    } catch (error) {
      this.logger.error('Error deleting template question', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_DELETE_FAILED, error);
    }
  }

  /**
   * Clone questions from master section to template section
   */
  async cloneQuestions(cloneDto: CloneQuestionsDto) {
    try {
      return await this.dataSource.transaction(async (manager) => {
        this.logger.log('Starting clone questions from master', { cloneDto });

      // 1. Verify template section exists
      const templateSection = await this.templateFormSectionRepo.findById(cloneDto.templateFormSectionId);
      if (!templateSection) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
          new Error(`Template form section not found: ${cloneDto.templateFormSectionId}`)
        );
      }

      // 2. Get questions from master section
      const masterQuestions = await this.masterQuestionRepo.findBySection(cloneDto.masterFormSectionId);

      if (!masterQuestions || masterQuestions.length === 0) {
        handleKnownErrors(
          ERROR_CODES.MASTER_QUESTION_NOT_FOUND,
          new Error(`No questions found in master section ${cloneDto.masterFormSectionId}`)
        );
      }

      // 3. Filter by specific IDs if provided
      const questionsToClone = cloneDto.specificQuestionIds && cloneDto.specificQuestionIds.length > 0
        ? masterQuestions.filter(q => cloneDto.specificQuestionIds!.includes(q.id))
        : masterQuestions;

      if (questionsToClone.length === 0) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_QUESTION_NO_QUESTIONS_TO_CLONE,
          new Error('No questions match the specified IDs')
        );
      }

      // 4. Clone questions
      const clonedQuestions: any[] = [];
      const skippedQuestions: any[] = [];
      
      // Get current max display order to start from
      const maxOrderResult = await manager
        .createQueryBuilder(TemplateQuestion, 'tq')
        .select('MAX(tq.display_order)', 'maxOrder')
        .where('tq.template_form_section_id = :sectionId', { sectionId: cloneDto.templateFormSectionId })
        .andWhere('tq.deleted_at IS NULL')
        .getRawOne();

      let currentDisplayOrder = (maxOrderResult?.maxOrder || 0) + 1;

      for (const masterQuestion of questionsToClone) {
        // Check if already cloned to this template section
        const existingClone = await manager
          .createQueryBuilder(TemplateQuestion, 'tq')
          .where('tq.master_question_id = :masterQuestionId', { masterQuestionId: masterQuestion.id })
          .andWhere('tq.template_form_section_id = :sectionId', { sectionId: cloneDto.templateFormSectionId })
          .andWhere('tq.deleted_at IS NULL')
          .getOne();

        if (existingClone) {
          if (cloneDto.override) {
            // Soft delete existing clone before re-cloning
            this.logger.log('Override flag set - soft deleting existing question clone', {
              masterQuestionId: masterQuestion.id,
              existingCloneId: existingClone.id,
            });
            await manager
              .createQueryBuilder()
              .update(TemplateQuestion)
              .set({ 
                deletedAt: new Date(),
                updatedBy: cloneDto.createdBy?.toString() || '-2'
              })
              .where('id = :id', { id: existingClone.id })
              .execute();
          } else {
            this.logger.warn('Question already cloned to this section, skipping', {
              masterQuestionId: masterQuestion.id,
              existingClone: existingClone.id,
            });
            skippedQuestions.push({ masterQuestionId: masterQuestion.id, reason: 'already_cloned' });
            continue;
          }
        }

        // Generate unique question code (transaction-aware)
        const questionCode = await this.generateUniqueQuestionCode(
          masterQuestion.questionCode,
          templateSection.programTemplateId,
          manager,
        );

        // Create template question with overrides applied
        const now = new Date();
        const templateQuestionDto: CreateTemplateQuestionDto = {
          templateFormSectionId: cloneDto.templateFormSectionId,
          masterQuestionId: masterQuestion.id,
          questionCode,
          bindingKey: masterQuestion.bindingKey,  // bindingKey cannot be overridden - it's for data mapping
          questionText: cloneDto.questionTextOverride ?? masterQuestion.questionText,
          questionType: cloneDto.questionTypeOverride ?? masterQuestion.questionType,
          answerType: cloneDto.answerTypeOverride ?? masterQuestion.answerType,
          answerLocation: cloneDto.answerLocationOverride ?? masterQuestion.answerLocation ?? undefined,
          optionConfig: cloneDto.optionConfigOverride ?? masterQuestion.optionConfig ?? undefined,
          config: cloneDto.configOverride ?? masterQuestion.config ?? {},
          conditionalConfig: cloneDto.conditionalConfigOverride ?? masterQuestion.conditionalConfig ?? undefined,
          displayOrder: cloneDto.displayOrderOverride ?? currentDisplayOrder++,
          createdBy: cloneDto.createdBy,
        };

        const cloned = await manager.save(TemplateQuestion, {
          ...templateQuestionDto,
          createdAt: now,
          updatedAt: now,
        });
        if (cloned) {
          clonedQuestions.push(cloned);
        }
      }

      this.logger.log('Clone questions completed successfully', {
        masterSectionId: cloneDto.masterFormSectionId,
        clonedQuestionsCount: clonedQuestions.length,
        skippedCount: skippedQuestions.length,
      });

      return {
        success: true,
        clonedQuestions: clonedQuestions.length,
        skipped: skippedQuestions.length,
        skippedDetails: skippedQuestions,
        questions: clonedQuestions,
      };
      });
    } catch (error) {
      this.logger.error('Error cloning questions', error);
      handleKnownErrors(ERROR_CODES.TEMPLATE_QUESTION_CLONE_FAILED, error);
    }
  }

  /**
   * Generate unique question code for template (transaction-aware)
   */
  private async generateUniqueQuestionCode(
    baseQuestionCode: string,
    programTemplateId: number,
    manager?: any,
  ): Promise<string> {
    let counter = 1;
    let questionCode = baseQuestionCode;

    while (true) {
      // Use manager if in transaction, otherwise use repository
      let existing;
      if (manager) {
        existing = await manager
          .createQueryBuilder(TemplateQuestion, 'tq')
          .leftJoin('tq.templateFormSection', 'tfs')
          .where('tq.question_code = :questionCode', { questionCode })
          .andWhere('tfs.program_template_id = :programTemplateId', { programTemplateId })
          .andWhere('tq.deleted_at IS NULL')
          .getOne();
      } else {
        existing = await this.templateQuestionRepo.findByQuestionCodeAndTemplate(
          questionCode,
          programTemplateId,
        );
      }

      if (!existing) {
        return questionCode;
      }

      questionCode = `${baseQuestionCode}_T${counter}`;
      counter++;

      // Prevent infinite loop
      if (counter > 100) {
        handleKnownErrors(
          ERROR_CODES.TEMPLATE_QUESTION_CODE_GENERATION_FAILED,
          new Error('Question code generation exceeded maximum attempts')
        );
      }
    }
  }
}
