import { Test, TestingModule } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { CommunicationConfigReadService } from './communication-config-read.service';
import { CommunicationTemplatesMasterRepository } from '../../repositories/communication-templates-master.repository';
import { CommunicationTemplatesRepository } from '../../repositories/communication-templates.repository';

const master = (id: number, step: string, accessKey: string, channel = 'email') => ({
  id,
  step,
  templateAccessKey: accessKey,
  templateName: `Template ${id}`,
  templateType: channel,
  templateId: `tpl-${id}`,
  sandboxTemplateId: null,
});

describe('CommunicationConfigReadService (rev 11 — per step, all templates)', () => {
  let service: CommunicationConfigReadService;
  let masterRepo: { findActiveSummariesByProgramTypeId: jest.Mock };
  let templatesRepo: { findAttachedStepsByProgram: jest.Mock };
  let dataSource: { createQueryBuilder: jest.Mock };
  let qb: any;

  beforeEach(async () => {
    masterRepo = { findActiveSummariesByProgramTypeId: jest.fn() };
    templatesRepo = { findAttachedStepsByProgram: jest.fn() };
    qb = {
      select: jest.fn().mockReturnThis(),
      from: jest.fn().mockReturnThis(),
      where: jest.fn().mockReturnThis(),
      getRawOne: jest.fn(),
    };
    dataSource = { createQueryBuilder: jest.fn(() => qb) };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CommunicationConfigReadService,
        { provide: CommunicationTemplatesMasterRepository, useValue: masterRepo },
        { provide: CommunicationTemplatesRepository, useValue: templatesRepo },
        { provide: DataSource, useValue: dataSource },
      ],
    }).compile();

    service = module.get(CommunicationConfigReadService);
  });

  describe('getByProgramType (preview)', () => {
    it('lists every template under every step; isEnabled = isNativeToStep', async () => {
      masterRepo.findActiveSummariesByProgramTypeId.mockResolvedValue([
        master(17, 'BLESSED', 'BLESSED_EMAIL_SEEKER'),
        master(50, 'INVOICE', 'INVOICE_EMAIL_SEEKER'),
      ]);

      const result = await service.getByProgramType(1);

      expect(result.steps.map((s) => s.value).sort()).toEqual(['BLESSED', 'INVOICE']);
      // Each step lists BOTH templates (full universe).
      for (const step of result.steps) {
        expect(step.templates).toHaveLength(2);
      }
      const blessed = result.steps.find((s) => s.value === 'BLESSED')!;
      const t17 = blessed.templates.find((t) => t.masterTemplateId === 17);
      const t50 = blessed.templates.find((t) => t.masterTemplateId === 50);
      expect(t17).toMatchObject({ isNativeToStep: true, isEnabled: true });
      expect(t50).toMatchObject({ isNativeToStep: false, isEnabled: false });
    });

    it('sorts native templates first within a step', async () => {
      masterRepo.findActiveSummariesByProgramTypeId.mockResolvedValue([
        master(50, 'INVOICE', 'INVOICE_EMAIL_SEEKER'),
        master(17, 'BLESSED', 'BLESSED_EMAIL_SEEKER'),
      ]);
      const result = await service.getByProgramType(1);
      const blessed = result.steps.find((s) => s.value === 'BLESSED')!;
      expect(blessed.templates[0].masterTemplateId).toBe(17); // native first
    });
  });

  describe('getByProgram (existing program)', () => {
    it('isEnabled comes from the clone attached_steps array (supports cross-step attachment)', async () => {
      qb.getRawOne.mockResolvedValue({ programTypeId: 1 });
      masterRepo.findActiveSummariesByProgramTypeId.mockResolvedValue([
        master(17, 'BLESSED', 'BLESSED_EMAIL_SEEKER'),
        master(50, 'INVOICE', 'INVOICE_EMAIL_SEEKER'),
      ]);
      // Admin attached the Invoice template (50) to the Blessed step too — its own
      // native INVOICE step was unticked.
      templatesRepo.findAttachedStepsByProgram.mockResolvedValue([
        { masterTemplateId: 17, attachedSteps: ['BLESSED'] },
        { masterTemplateId: 50, attachedSteps: ['BLESSED'] },
      ]);

      const result = await service.getByProgram(124);

      const blessed = result.steps.find((s) => s.value === 'BLESSED')!;
      expect(blessed.templates.find((t) => t.masterTemplateId === 50)).toMatchObject({
        isEnabled: true, // attached to Blessed
        isNativeToStep: false,
      });
      const invoice = result.steps.find((s) => s.value === 'INVOICE')!;
      expect(invoice.templates.find((t) => t.masterTemplateId === 50)).toMatchObject({
        isEnabled: false, // not attached to its own step in this config
        isNativeToStep: true,
      });
    });

    it('returns empty steps when the program type cannot be resolved', async () => {
      qb.getRawOne.mockResolvedValue(undefined);
      const result = await service.getByProgram(999);
      expect(result.steps).toEqual([]);
    });

    it('isEnabled is false when the master has no clone row for the program', async () => {
      qb.getRawOne.mockResolvedValue({ programTypeId: 1 });
      masterRepo.findActiveSummariesByProgramTypeId.mockResolvedValue([
        master(17, 'BLESSED', 'BLESSED_EMAIL_SEEKER'),
      ]);
      templatesRepo.findAttachedStepsByProgram.mockResolvedValue([]);

      const result = await service.getByProgram(124);

      const blessed = result.steps.find((s) => s.value === 'BLESSED')!;
      expect(blessed.templates[0].isEnabled).toBe(false);
    });
  });
});
