import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { ProgramStepTemplateMap } from 'src/common/entities/program-step-template-map.entity';
import { CommunicationTemplatesMaster } from 'src/common/entities/communication-templates-master.entity';
import { CommunicationTemplates } from 'src/common/entities/communication-templates.entity';
import { handleKnownErrors } from 'src/common/utils/handle-error.util';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { CommunicationStepSelection } from '../interfaces/communication-config.interface';

@Injectable()
export class ProgramStepTemplateMapRepository {
  private readonly logger = new Logger(ProgramStepTemplateMapRepository.name);

  constructor(
    @InjectRepository(ProgramStepTemplateMap)
    private readonly repository: Repository<ProgramStepTemplateMap>,
    @InjectRepository(CommunicationTemplatesMaster)
    private readonly masterRepository: Repository<CommunicationTemplatesMaster>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * All enabled (step, masterTemplateId) attachments for a program.
   */
  async findEnabledByProgram(programId: number): Promise<ProgramStepTemplateMap[]> {
    try {
      return await this.repository.find({
        where: { programId, isEnabled: true },
      });
    } catch (error) {
      this.logger.error(
        `Error finding enabled attachments for program ${programId}: ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_GET_FAILED, error);
    }
  }

  /**
   * Master template ids enabled for one (program, step). Used by the native send gate
   * and Phase 2 step-driven dispatch.
   */
  async findEnabledMasterIdsByProgramStep(programId: number, step: string): Promise<number[]> {
    try {
      const rows = await this.repository.find({
        where: { programId, step: step as any, isEnabled: true },
        select: ['masterTemplateId'],
      });
      return rows.map((row) => Number(row.masterTemplateId));
    } catch (error) {
      this.logger.error(
        `Error finding enabled master ids for program ${programId} step ${step}: ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_GET_FAILED, error);
    }
  }

  /**
   * Seed native defaults for a freshly-created program: every active master of the type
   * attached (enabled) to its own native step. Idempotent via the unique constraint.
   */
  async seedDefaultsForProgram(
    programId: number,
    programTypeId: number,
    userId?: number,
  ): Promise<void> {
    try {
      const masters = await this.masterRepository
        .createQueryBuilder('master')
        .select(['master.id AS id', 'master.step AS step'])
        .where('master.programTypeId = :programTypeId', { programTypeId })
        .andWhere('master.isActive = true')
        .andWhere('master.step IS NOT NULL')
        .getRawMany();

      if (!masters.length) {
        return;
      }
      const rows = masters.map(
        (master) =>
          new ProgramStepTemplateMap({
            programId,
            step: master.step,
            masterTemplateId: Number(master.id),
            isEnabled: true,
            createdBy: userId ?? null,
            updatedBy: userId ?? null,
          }),
      );
      await this.repository
        .createQueryBuilder()
        .insert()
        .into(ProgramStepTemplateMap)
        .values(rows)
        .orIgnore() // ON CONFLICT DO NOTHING on the unique constraint
        .execute();
    } catch (error) {
      this.logger.error(
        `Error seeding defaults for program ${programId} (type ${programTypeId}): ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_SAVE_FAILED, error);
    }
  }

  /**
   * Replace the program's attachments with the supplied per-step selection.
   * For each step in the payload: enable the listed ids (upsert is_enabled=true) and
   * disable any currently-enabled attachment for that step not in the list. Steps absent
   * from the payload are left untouched. Runs in a transaction.
   */
  async replaceForProgram(
    programId: number,
    perStep: CommunicationStepSelection[],
    userId?: number,
  ): Promise<void> {
    try {
      await this.dataSource.transaction(async (manager) => {
        const mapRepo = manager.getRepository(ProgramStepTemplateMap);
        for (const sel of perStep) {
          const ids = (sel.enabledMasterTemplateIds ?? []).map((id) => Number(id));

          // Disable enabled rows for this step that are no longer selected.
          const disableQb = mapRepo
            .createQueryBuilder()
            .update(ProgramStepTemplateMap)
            .set({ isEnabled: false, updatedBy: userId ?? undefined })
            .where('program_id = :programId', { programId })
            .andWhere('step = :step', { step: sel.step })
            .andWhere('is_enabled = true');
          if (ids.length > 0) {
            disableQb.andWhere('master_template_id NOT IN (:...ids)', { ids });
          }
          await disableQb.execute();

          if (ids.length === 0) {
            continue;
          }

          // Upsert the selected ids as enabled.
          const values = ids.map((masterTemplateId) => ({
            programId,
            step: sel.step as any,
            masterTemplateId,
            isEnabled: true,
            createdBy: userId ?? null,
            updatedBy: userId ?? null,
          }));
          await mapRepo
            .createQueryBuilder()
            .insert()
            .into(ProgramStepTemplateMap)
            .values(values)
            .orUpdate(['is_enabled', 'updated_by'], ['program_id', 'step', 'master_template_id'])
            .execute();
        }

        // Sync clone soft-delete on hdb_communication_templates:
        //   - soft-delete clones whose master has NO enabled map row for this program
        //   - restore clones that have at least one enabled map row but were soft-deleted
        const cloneRepo = manager.getRepository(CommunicationTemplates);
        const enabledRows = await mapRepo
          .createQueryBuilder('map')
          .select('map.master_template_id', 'masterTemplateId')
          .where('map.program_id = :programId', { programId })
          .andWhere('map.is_enabled = true')
          .groupBy('map.master_template_id')
          .getRawMany();
        const enabledMasterIds = enabledRows
          .map((row) => Number(row.masterTemplateId))
          .filter((id) => !Number.isNaN(id));

        // Soft-delete clones whose master is NOT in the enabled set.
        const softDeleteQb = cloneRepo
          .createQueryBuilder()
          .update(CommunicationTemplates)
          .set({ deletedAt: () => 'NOW()', updatedBy: userId ?? undefined })
          .where('program_id = :programId', { programId })
          .andWhere('deleted_at IS NULL');
        if (enabledMasterIds.length > 0) {
          softDeleteQb.andWhere('master_template_id NOT IN (:...ids)', {
            ids: enabledMasterIds,
          });
        }
        await softDeleteQb.execute();

        // Restore clones whose master IS in the enabled set.
        if (enabledMasterIds.length > 0) {
          await cloneRepo
            .createQueryBuilder()
            .update(CommunicationTemplates)
            .set({ deletedAt: () => 'NULL', updatedBy: userId ?? undefined })
            .where('program_id = :programId', { programId })
            .andWhere('deleted_at IS NOT NULL')
            .andWhere('master_template_id IN (:...ids)', { ids: enabledMasterIds })
            .execute();
        }
      });
    } catch (error) {
      this.logger.error(
        `Error replacing attachments for program ${programId}: ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_SAVE_FAILED, error);
    }
  }
}
