import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository, IsNull, In } from 'typeorm';
import { ProgramType } from 'src/common/entities/program-type.entity';
import { ProgramTemplateRepository } from './program-template.repository';
import { TemplateQuestionRepository } from 'src/template-question/template-question.repository';
import { CreateProgramTemplateDto } from './dto/create-program-template.dto';
import { UpdateProgramTemplateDto } from './dto/update-program-template.dto';
import { FilterProgramTemplateDto } from './dto/filter-program-template.dto';
import { UpdateTemplateFormDto } from './dto/update-template-form.dto';
import { InifniNotFoundException } from 'src/common/exceptions/infini-notfound-exception';
import InifniBadRequestException from 'src/common/exceptions/infini-badrequest-exception';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { programTemplateConstMessages } from 'src/common/constants/strings-constants';
import { AppLoggerService } from 'src/common/services/logger.service';
import { TemplateStatus } from 'src/common/enum/template-status.enum';
import { TemplateFormSection } from 'src/common/entities/template-form-section.entity';
import { TemplateQuestion } from 'src/common/entities/template-question.entity';
import { MasterQuestion } from 'src/common/entities/master-question.entity';
import { User } from 'src/common/entities/user.entity';
import { ProgramTemplate } from 'src/common/entities/program-template.entity';

@Injectable()
export class ProgramTemplateService {
  constructor(
    private readonly programTemplateRepo: ProgramTemplateRepository,
    private readonly templateQuestionRepo: TemplateQuestionRepository,
    private readonly logger: AppLoggerService,
    @InjectRepository(ProgramType)
    private readonly programTypeRepo: Repository<ProgramType>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Get all program templates with filters
   */
  async findAll(filters: FilterProgramTemplateDto) {
    try {
      this.logger.log('Finding all program templates', { filters });
      return await this.programTemplateRepo.findAll(filters);
    } catch (error) {
      this.logger.error('Error finding program templates', error);
      throw error;
    }
  }

  /**
   * Get a single program template by ID with sections and questions
   */
  async findById(id: number) {
    try {
      this.logger.log('Finding program template by ID', { id });
      const template = await this.programTemplateRepo.findById(id);
      
      if (!template) {
        throw new InifniNotFoundException(
          ERROR_CODES.PROGRAM_TEMPLATE_NOT_FOUND,
          null,
          null,
          id.toString()
        );
      }

      // Load questions for each section
      if (template.sections && template.sections.length > 0) {
        for (const section of template.sections) {
          const questions = await this.templateQuestionRepo.findByTemplateSection(section.id);
          section['questions'] = questions;
        }
      }

      return template;
    } catch (error) {
      this.logger.error('Error finding program template by ID', error);
      throw error;
    }
  }

  /**
   * Get templates by program type
   */
  async findByProgramType(programTypeId: number) {
    try {
      this.logger.log('Finding templates by program type', { programTypeId });
      return await this.programTemplateRepo.findByProgramType(programTypeId);
    } catch (error) {
      this.logger.error('Error finding templates by program type', error);
      throw error;
    }
  }

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

      // Validate that program type exists
      const programType = await this.programTypeRepo.findOne({
        where: { id: createDto.programTypeId },
      });

      if (!programType) {
        throw new InifniBadRequestException(
          ERROR_CODES.PROGRAM_TEMPLATE_INVALID_PROGRAM_TYPE,
          null,
          null,
          createDto.programTypeId.toString(),
        );
      }

      const template = await this.programTemplateRepo.create(createDto);
      this.logger.log('Program template created successfully', { templateId: template.id });
      
      return template;
    } catch (error) {
      this.logger.error('Error creating program template', error);
      throw error;
    }
  }

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

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

      // Prevent status update if template is published and has sections
      if (template.status === TemplateStatus.PUBLISHED && updateDto.status && updateDto.status !== TemplateStatus.PUBLISHED) {
        throw new InifniBadRequestException(
          'PROGRAM_TEMPLATE_PUBLISHED_CANNOT_CHANGE_STATUS',
          null,
          null,
          'Cannot change status of a published template. Archive it instead.'
        );
      }

      const updated = await this.programTemplateRepo.update(id, updateDto);
      this.logger.log('Program template updated successfully', { templateId: id });
      
      return updated;
    } catch (error) {
      this.logger.error('Error updating program template', error);
      throw error;
    }
  }

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

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

      // Prevent deletion if published
      if (template.status === TemplateStatus.PUBLISHED) {
        throw new InifniBadRequestException(
          'PROGRAM_TEMPLATE_PUBLISHED_CANNOT_DELETE',
          null,
          null,
          'Cannot delete a published template. Archive it instead.'
        );
      }

      await this.programTemplateRepo.softDelete(id);
      this.logger.log('Program template deleted successfully', { templateId: id });
      
      return { deleted: true };
    } catch (error) {
      this.logger.error('Error deleting program template', error);
      throw error;
    }
  }

  /**
   * Publish a template
   */
  async publish(id: number, updatedBy?: number) {
    try {
      this.logger.log('Publishing program template', { id });

      const template = await this.findById(id);

      if (template.status === TemplateStatus.PUBLISHED) {
        throw new InifniBadRequestException(
          ERROR_CODES.PROGRAM_TEMPLATE_ALREADY_PUBLISHED,
          null,
          null,
          programTemplateConstMessages.TEMPLATE_ALREADY_PUBLISHED,
        );
      }

      // Verify template has at least one section
      if (!template.sections || template.sections.length === 0) {
        throw new InifniBadRequestException(
          'PROGRAM_TEMPLATE_NO_SECTIONS',
          null,
          null,
          'Cannot publish template without sections. Clone from master first.'
        );
      }

      const updated = await this.programTemplateRepo.update(id, {
        status: TemplateStatus.PUBLISHED,
        updatedBy,
      });

      this.logger.log('Program template published successfully', { templateId: id });
      return updated;
    } catch (error) {
      this.logger.error('Error publishing program template', error);
      throw error;
    }
  }

  /**
   * Archive a template
   */
  async archive(id: number, updatedBy?: number) {
    try {
      this.logger.log('Archiving program template', { id });

      const template = await this.findById(id);

      if (template.status === TemplateStatus.ARCHIVED) {
        throw new InifniBadRequestException(
          ERROR_CODES.PROGRAM_TEMPLATE_ALREADY_ARCHIVED,
          null,
          null,
          programTemplateConstMessages.TEMPLATE_ALREADY_ARCHIVED,
        );
      }

      const updated = await this.programTemplateRepo.update(id, {
        status: TemplateStatus.ARCHIVED,
        isActive: false,
        updatedBy,
      });

      this.logger.log('Program template archived successfully', { templateId: id });
      return updated;
    } catch (error) {
      this.logger.error('Error archiving program template', error);
      throw error;
    }
  }

  /**
   * Update template form structure (sections and questions)
   * Option A workflow - direct template update
   */
  async updateTemplateForm(dto: UpdateTemplateFormDto, userId: number) {
    this.logger.log('Starting template form update', { dto, userId });

    return await this.dataSource.transaction(async (manager) => {
      // Counters for summary
      let sectionsDeleted = 0;
      let sectionsUpdated = 0;
      let sectionsAdded = 0;
      let questionsDeleted = 0;
      let questionsUpdated = 0;
      let questionsAdded = 0;

      // Validate template exists
      const template = await manager.findOne(ProgramTemplate, {
        where: { id: dto.programTemplateId, deletedAt: IsNull() },
      });

      if (!template) {
        throw new InifniNotFoundException(
          ERROR_CODES.PROGRAM_TEMPLATE_NOT_FOUND,
          null,
          null,
          `No template found with ID ${dto.programTemplateId}`,
        );
      }

      // Validate user exists
      const updatedUser = await manager.findOne(User, {
        where: { id: userId },
      });

      if (!updatedUser) {
        throw new InifniNotFoundException(
          ERROR_CODES.USER_NOTFOUND,
          null,
          null,
          `User not found. The user with ID ${userId} does not exist.`,
        );
      }

      // ========================================
      // PHASE A: DELETE SECTIONS
      // ========================================
      if (dto.deleteSectionIds && dto.deleteSectionIds.length > 0) {
        this.logger.log('Phase A: Deleting sections', { count: dto.deleteSectionIds.length });

        for (const sectionId of dto.deleteSectionIds) {
          // Validate section belongs to this template
          const section = await manager.findOne(TemplateFormSection, {
            where: {
              id: sectionId,
              programTemplateId: dto.programTemplateId,
              deletedAt: IsNull(),
            },
          });

          if (!section) {
            throw new InifniNotFoundException(
              ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
              null,
              null,
              `Section ${sectionId} not found or doesn't belong to template ${dto.programTemplateId}`,
            );
          }

          // Soft-delete all questions in this section
          const questions = await manager.find(TemplateQuestion, {
            where: {
              templateFormSectionId: sectionId,
              deletedAt: IsNull(),
            },
          });

          if (questions.length > 0) {
            const questionIds = questions.map((q) => q.id);
            await manager.softDelete(TemplateQuestion, questionIds);
            questionsDeleted += questionIds.length;
          }

          // Soft-delete the section
          await manager.softDelete(TemplateFormSection, sectionId);
          sectionsDeleted++;
        }

        this.logger.log('Phase A complete', { sectionsDeleted, questionsDeleted });
      }

      // ========================================
      // PHASE B: UPDATE SECTIONS
      // ========================================
      if (dto.updateSections && dto.updateSections.length > 0) {
        this.logger.log('Phase B: Updating sections', { count: dto.updateSections.length });

        for (const sectionUpdate of dto.updateSections) {
          // Validate section exists and belongs to template
          const section = await manager.findOne(TemplateFormSection, {
            where: {
              id: sectionUpdate.templateFormSectionId,
              programTemplateId: dto.programTemplateId,
              deletedAt: IsNull(),
            },
          });

          if (!section) {
            throw new InifniNotFoundException(
              ERROR_CODES.TEMPLATE_FORM_SECTION_NOT_FOUND,
              null,
              null,
              `Section ${sectionUpdate.templateFormSectionId} not found in template ${dto.programTemplateId}`,
            );
          }

          // B1: Delete questions from section
          if (sectionUpdate.deleteQuestionIds && sectionUpdate.deleteQuestionIds.length > 0) {
            this.logger.log('Phase B1: Deleting questions from section', {
              sectionId: sectionUpdate.templateFormSectionId,
              count: sectionUpdate.deleteQuestionIds.length,
            });

            for (const questionId of sectionUpdate.deleteQuestionIds) {
              const question = await manager.findOne(TemplateQuestion, {
                where: {
                  id: questionId,
                  templateFormSectionId: sectionUpdate.templateFormSectionId,
                  deletedAt: IsNull(),
                },
              });

              if (!question) {
                throw new InifniNotFoundException(
                  ERROR_CODES.TEMPLATE_QUESTION_NOT_FOUND,
                  null,
                  null,
                  `Question ${questionId} not found in section ${sectionUpdate.templateFormSectionId}`,
                );
              }

              await manager.softDelete(TemplateQuestion, questionId);
              questionsDeleted++;
            }
          }

          // B2: Update existing questions
          if (sectionUpdate.updateQuestions && sectionUpdate.updateQuestions.length > 0) {
            this.logger.log('Phase B2: Updating questions', {
              sectionId: sectionUpdate.templateFormSectionId,
              count: sectionUpdate.updateQuestions.length,
            });

            for (const questionUpdate of sectionUpdate.updateQuestions) {
              const question = await manager.findOne(TemplateQuestion, {
                where: {
                  id: questionUpdate.templateQuestionId,
                  templateFormSectionId: sectionUpdate.templateFormSectionId,
                  deletedAt: IsNull(),
                },
              });

              if (!question) {
                throw new InifniNotFoundException(
                  ERROR_CODES.TEMPLATE_QUESTION_NOT_FOUND,
                  null,
                  null,
                  `Question ${questionUpdate.templateQuestionId} not found in section`,
                );
              }

              // Build update object
              const updateData: any = { updatedBy: userId };
              if (questionUpdate.questionText !== undefined)
                updateData.questionText = questionUpdate.questionText;
              if (questionUpdate.questionType !== undefined)
                updateData.questionType = questionUpdate.questionType;
              if (questionUpdate.answerType !== undefined)
                updateData.answerType = questionUpdate.answerType;
              if (questionUpdate.config !== undefined) updateData.config = questionUpdate.config || {};
              if (questionUpdate.optionConfig !== undefined)
                updateData.optionConfig = questionUpdate.optionConfig;
              if (questionUpdate.conditionalConfig !== undefined)
                updateData.conditionalConfig = questionUpdate.conditionalConfig;
              if (questionUpdate.displayOrder !== undefined)
                updateData.displayOrder = questionUpdate.displayOrder;

              if (Object.keys(updateData).length > 1) {
                await manager.update(TemplateQuestion, questionUpdate.templateQuestionId, updateData);
                questionsUpdated++;
              }
            }
          }

          // B3: Add new questions to section
          if (sectionUpdate.addQuestions && sectionUpdate.addQuestions.length > 0) {
            this.logger.log('Phase B3: Adding questions to section', {
              sectionId: sectionUpdate.templateFormSectionId,
              count: sectionUpdate.addQuestions.length,
            });

            // Batch fetch master questions
            const masterQuestionIds = sectionUpdate.addQuestions
              .map((q) => q.masterQuestionId)
              .filter((id) => id != null);

            this.logger.log('DEBUG: Fetching master questions', {
              requestedIds: masterQuestionIds,
              idsTypes: masterQuestionIds.map(id => ({ id, type: typeof id })),
            });
            const masterQuestionsMap = new Map<string, MasterQuestion>();
            if (masterQuestionIds.length > 0) {
              const masterQuestions = await manager.find(MasterQuestion, {
                where: { id: In(masterQuestionIds), deletedAt: IsNull() },
              });
              this.logger.log('DEBUG: Master questions fetched from DB', {
                count: masterQuestions.length,
                foundIds: masterQuestions.map(mq => ({ id: mq.id, type: typeof mq.id })),
              });
              masterQuestions.forEach((mq) => masterQuestionsMap.set(String(mq.id), mq));
              this.logger.log('DEBUG: Master questions map keys', {
                mapKeys: Array.from(masterQuestionsMap.keys()),
              });
            }

            for (const addQuestion of sectionUpdate.addQuestions) {
              this.logger.log('DEBUG: Looking up master question', {
                lookupId: addQuestion.masterQuestionId,
                lookupType: typeof addQuestion.masterQuestionId,
                mapHasKey: masterQuestionsMap.has(String(addQuestion.masterQuestionId)),
              });
              const masterQuestion = masterQuestionsMap.get(String(addQuestion.masterQuestionId));
              if (!masterQuestion) {
                throw new InifniNotFoundException(
                  ERROR_CODES.MASTER_QUESTION_NOT_FOUND,
                  null,
                  null,
                  `Master question ${addQuestion.masterQuestionId} not found`,
                );
              }

              // Create template question
              const newQuestion = manager.create(TemplateQuestion, {
                templateFormSectionId: sectionUpdate.templateFormSectionId,
                masterQuestionId: masterQuestion.id,
                questionCode: masterQuestion.questionCode,
                bindingKey: masterQuestion.bindingKey,
                questionText: addQuestion.override?.label || masterQuestion.questionText,
                questionType: masterQuestion.questionType,
                answerType: masterQuestion.answerType,
                answerLocation: masterQuestion.answerLocation,
                config: addQuestion.override?.config || masterQuestion.config || {},
                optionConfig: addQuestion.override?.optionConfig || masterQuestion.optionConfig || null,
                conditionalConfig: addQuestion.override?.conditionalConfig ?? masterQuestion?.conditionalConfig ?? null,
                displayOrder: addQuestion.displayOrder || 1,
                createdBy: userId,
                updatedBy: userId,
                createdAt: new Date(),
                updatedAt: new Date(),
              });

              await manager.save(TemplateQuestion, newQuestion);
              questionsAdded++;
            }
          }

          // B4: Update section metadata
          const sectionUpdateData: any = { updatedBy: userId };
          if (sectionUpdate.name !== undefined) sectionUpdateData.name = sectionUpdate.name;
          if (sectionUpdate.description !== undefined)
            sectionUpdateData.description = sectionUpdate.description;
          if (sectionUpdate.displayOrder !== undefined)
            sectionUpdateData.displayOrder = sectionUpdate.displayOrder;
          if (sectionUpdate.conditionalConfig !== undefined)
            sectionUpdateData.conditionalConfig = sectionUpdate.conditionalConfig;

          if (Object.keys(sectionUpdateData).length > 1) {
            await manager.update(
              TemplateFormSection,
              sectionUpdate.templateFormSectionId,
              sectionUpdateData,
            );
            sectionsUpdated++;
          }
        }

        this.logger.log('Phase B complete', { sectionsUpdated, questionsUpdated, questionsAdded });
      }

      // ========================================
      // PHASE C: ADD NEW SECTIONS
      // ========================================
      if (dto.addSections && dto.addSections.length > 0) {
        this.logger.log('Phase C: Adding new sections', { count: dto.addSections.length });

        for (const addSection of dto.addSections) {
          // Create section
          const newSection = manager.create(TemplateFormSection, {
            programTemplateId: dto.programTemplateId,
            masterFormSectionId: addSection.masterFormSectionId || null,
            sectionKey: addSection.sectionKey,
            name: addSection.name,
            description: addSection.description || null,
            parentSectionId: addSection.parentSectionId || null,
            conditionalConfig: addSection.conditionalConfig || null,
            displayOrder: addSection.displayOrder,
            createdBy: userId,
            updatedBy: userId,
          });

          const savedSection = await manager.save(TemplateFormSection, newSection);
          sectionsAdded++;

          // Add questions to new section
          if (addSection.questions && addSection.questions.length > 0) {
            this.logger.log('Adding questions to new section', {
              sectionId: savedSection.id,
              count: addSection.questions.length,
            });

            const masterQuestionIds = addSection.questions
              .map((q) => q.masterQuestionId)
              .filter((id) => id != null);

            const masterQuestionsMap = new Map<number, MasterQuestion>();
            if (masterQuestionIds.length > 0) {
              const masterQuestions = await manager.find(MasterQuestion, {
                where: { id: In(masterQuestionIds), deletedAt: IsNull() },
              });
              masterQuestions.forEach((mq) => masterQuestionsMap.set(mq.id, mq));
            }

            for (const addQuestion of addSection.questions) {
              const masterQuestion = masterQuestionsMap.get(addQuestion.masterQuestionId);
              if (!masterQuestion) {
                throw new InifniNotFoundException(
                  ERROR_CODES.MASTER_QUESTION_NOT_FOUND,
                  null,
                  null,
                  `Master question ${addQuestion.masterQuestionId} not found`,
                );
              }

              const newQuestion = manager.create(TemplateQuestion, {
                templateFormSectionId: savedSection.id,
                masterQuestionId: masterQuestion.id,
                questionCode: masterQuestion.questionCode,
                bindingKey: masterQuestion.bindingKey,
                questionText: addQuestion.override?.label || masterQuestion.questionText,
                questionType: masterQuestion.questionType,
                answerType: masterQuestion.answerType,
                answerLocation: masterQuestion.answerLocation,
                config: addQuestion.override?.config || masterQuestion.config || {},
                optionConfig: addQuestion.override?.optionConfig || masterQuestion.optionConfig || null,
                conditionalConfig: addQuestion.override?.conditionalConfig ?? masterQuestion.conditionalConfig ?? null,
                displayOrder: addQuestion.displayOrder || 1,
                createdBy: userId,
                updatedBy: userId,
              });

              await manager.save(TemplateQuestion, newQuestion);
              questionsAdded++;
            }
          }
        }

        this.logger.log('Phase C complete', { sectionsAdded, questionsAdded });
      }

      // Return summary
      return {
        message: programTemplateConstMessages.TEMPLATE_FORM_UPDATED,
        data: {
          programTemplateId: dto.programTemplateId,
          sectionsDeleted,
          sectionsUpdated,
          sectionsAdded,
          questionsDeleted,
          questionsUpdated,
          questionsAdded,
        },
      };
    });
  }
}
