import { Injectable, Logger } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { handleKnownErrors } from 'src/common/utils/handle-error.util';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { CommunicationTemplatesMasterRepository } from '../../repositories/communication-templates-master.repository';
import { CommunicationTemplatesRepository } from '../../repositories/communication-templates.repository';
import { CommunicationTemplatesMaster } from 'src/common/entities/communication-templates-master.entity';
import {
  CommunicationConfigResponse,
  CommunicationConfigStep,
} from '../../interfaces/communication-config.interface';
import { humanizeStep } from '../../utils/communication-step.util';

/**
 * Read endpoints for the Add Program communication step (rev 11).
 *
 * Per-(program, master) attachment lives on `hdb_communication_templates.attached_steps`
 * (a text[] of step values). `isEnabled` for (step, master) is "step ∈ clone.attached_steps".
 *
 *  - by program type (new program preview): isEnabled = isNativeToStep (the seed default).
 *  - by program (existing): isEnabled = step ∈ clone.attached_steps for (programId, masterId).
 */
@Injectable()
export class CommunicationConfigReadService {
  private readonly logger = new Logger(CommunicationConfigReadService.name);

  constructor(
    private readonly masterRepo: CommunicationTemplatesMasterRepository,
    private readonly templatesRepo: CommunicationTemplatesRepository,
    private readonly dataSource: DataSource,
  ) {}

  async getByProgramType(programTypeId: number): Promise<CommunicationConfigResponse> {
    try {
      const masters = await this.masterRepo.findActiveSummariesByProgramTypeId(programTypeId);
      // Preview: a template is enabled for a step iff it is native to that step.
      return { steps: this.buildSteps(masters, () => null) };
    } catch (error) {
      this.logger.error(
        `Error reading communication config for programTypeId ${programTypeId}: ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_GET_FAILED, error);
    }
  }

  async getByProgram(programId: number): Promise<CommunicationConfigResponse> {
    try {
      const programTypeId = await this.getProgramTypeId(programId);
      if (!programTypeId) {
        return { steps: [] };
      }
      const masters = await this.masterRepo.findActiveSummariesByProgramTypeId(programTypeId);
      const attached = await this.templatesRepo.findAttachedStepsByProgram(programId);
      const stepsByMaster = new Map<number, Set<string>>();
      for (const row of attached) {
        stepsByMaster.set(row.masterTemplateId, new Set(row.attachedSteps));
      }
      return {
        steps: this.buildSteps(masters, (step, masterId) => {
          const steps = stepsByMaster.get(masterId);
          return steps ? steps.has(step) : false;
        }),
      };
    } catch (error) {
      this.logger.error(
        `Error reading communication config for program ${programId}: ${error.message}`,
        error.stack,
      );
      handleKnownErrors(ERROR_CODES.COMMUNICATION_TEMPLATE_GET_FAILED, error);
    }
  }

  /**
   * Build the steps array: one step per distinct non-null master step, each listing the
   * full template universe. `enabledResolver` returns the isEnabled value for a (step, masterId)
   * pair, or null to fall back to isNativeToStep (the preview default).
   */
  private buildSteps(
    masters: CommunicationTemplatesMaster[],
    enabledResolver: (step: string, masterId: number) => boolean | null,
  ): CommunicationConfigStep[] {
    const universe = masters.filter((master) => !!master); // all active masters for the type
    const stepValues = Array.from(
      new Set(universe.map((master) => master.step).filter((step): step is any => !!step)),
    ).sort((a, b) => String(a).localeCompare(String(b)));

    return stepValues.map((stepValue) => {
      const templates = universe
        .map((master) => {
          const masterId = Number(master.id);
          const isNativeToStep =
            (master.step as unknown as string) === (stepValue as unknown as string);
          const resolved = enabledResolver(stepValue as unknown as string, masterId);
          return {
            masterTemplateId: masterId,
            templateAccessKey: master.templateAccessKey as unknown as string,
            templateName: master.templateName ?? null,
            channel: master.templateType as unknown as string,
            templateId: master.templateId ?? null,
            sandboxTemplateId: master.sandboxTemplateId ?? null,
            isEnabled: resolved === null ? isNativeToStep : resolved,
            isNativeToStep,
          };
        })
        .sort((a, b) => {
          // Native first, then by access key.
          if (a.isNativeToStep !== b.isNativeToStep) {
            return a.isNativeToStep ? -1 : 1;
          }
          return a.templateAccessKey.localeCompare(b.templateAccessKey);
        });
      return {
        value: stepValue as unknown as string,
        label: humanizeStep(stepValue as unknown as string),
        templates,
      };
    });
  }

  private async getProgramTypeId(programId: number): Promise<number | null> {
    const row = await this.dataSource
      .createQueryBuilder()
      .select('p.program_type_id', 'programTypeId')
      .from('program_v1', 'p')
      .where('p.id = :programId', { programId })
      .getRawOne();
    return row?.programTypeId ? Number(row.programTypeId) : null;
  }
}
