import { CatalogEntityKind } from '@prisma/client';
import {
  PROSTHESIS_TYPES,
  buildProsthesisStepCodes,
} from '../../../prisma/catalog-seed-data';
import { generateLabCaseTasks } from './lab-case-task.generator';

function buildMockTx(options: {
  existingCount?: number;
  toothProsthesisRows: Array<{
    treatmentDetailId?: string | null;
    lineId?: string | null;
    sourceKey?: string;
    tooth: string;
    prosthesisTypeCode: string;
    selectionGroupId?: string;
    treatmentType?: string;
  }>;
  prosthesisTypes: Array<{
    code: string;
    steps: Array<{ stepOrder: number; workflowStepCode: string }>;
  }>;
  stepLabels?: Record<string, string>;
}) {
  const created: unknown[] = [];

  const tx = {
    labCaseTask: {
      count: jest.fn().mockResolvedValue(options.existingCount ?? 0),
      createMany: jest.fn().mockImplementation(({ data }) => {
        created.push(...data);
        return { count: data.length };
      }),
    },
    labCaseToothProsthesis: {
      findMany: jest.fn().mockResolvedValue(
        options.toothProsthesisRows.map((row) => ({
          treatmentDetailId: row.treatmentDetailId ?? null,
          lineId: row.lineId ?? null,
          sourceKey: row.sourceKey ?? row.treatmentDetailId ?? row.lineId ?? '',
          treatmentType: row.treatmentType ?? 'prosthesis',
          tooth: row.tooth,
          prosthesisTypeCode: row.prosthesisTypeCode,
          selectionGroupId: row.selectionGroupId ?? '',
          detail: row.treatmentDetailId
            ? {
                id: row.treatmentDetailId,
                treatmentType: row.treatmentType ?? 'prosthesis',
                toothSelectionGroups: null,
              }
            : null,
          line: row.lineId
            ? {
                id: row.lineId,
                treatmentType: row.treatmentType ?? 'prosthesis',
                toothSelectionGroups: null,
              }
            : null,
        })),
      ),
    },
    prosthesisType: {
      findMany: jest.fn().mockResolvedValue(
        options.prosthesisTypes.map((type) => ({
          code: type.code,
          isActive: true,
          steps: type.steps.map((step) => ({
            stepOrder: step.stepOrder,
            labWorkflowStep: { code: step.workflowStepCode },
          })),
        })),
      ),
    },
    catalogTranslation: {
      findMany: jest.fn().mockImplementation(({ where }) => {
        const codes = where.entityCode?.in ?? [];
        return codes.map((code: string) => ({
          entityCode: code,
          locale: 'en',
          label: options.stepLabels?.[code] ?? code,
          entityKind: CatalogEntityKind.LAB_WORKFLOW_STEP,
        }));
      }),
    },
  };

  return { tx, created };
}

function stepsFromSeed(code: string) {
  const seed = PROSTHESIS_TYPES.find((type) => type.code === code);
  if (!seed) {
    throw new Error(`Unknown prosthesis code: ${code}`);
  }
  return buildProsthesisStepCodes(seed).map((workflowStepCode, index) => ({
    stepOrder: index + 1,
    workflowStepCode,
  }));
}

describe('generateLabCaseTasks', () => {
  it('creates tasks for pfm_crown with universal and type-specific steps', async () => {
    const pfmSteps = stepsFromSeed('pfm_crown');
    const { tx, created } = buildMockTx({
      toothProsthesisRows: [
        {
          treatmentDetailId: 'detail-1',
          tooth: '14',
          prosthesisTypeCode: 'pfm_crown',
        },
      ],
      prosthesisTypes: [{ code: 'pfm_crown', steps: pfmSteps }],
      stepLabels: { intraoral_scan: 'Intraoral Scan', packing: 'Packing' },
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-case-1', 'en');

    expect(count).toBe(pfmSteps.length);
    expect(created).toHaveLength(pfmSteps.length);
    expect(created[0]).toMatchObject({
      teeth: ['14'],
      prosthesisTypeCode: 'pfm_crown',
      workflowStepCode: 'intraoral_scan',
      stepLabel: 'Intraoral Scan',
      status: 'IN_PROGRESS',
    });
    const stepCodes = (created as Array<{ workflowStepCode: string }>).map(
      (row) => row.workflowStepCode,
    );
    expect(stepCodes).toEqual(pfmSteps.map((step) => step.workflowStepCode));
    expect(stepCodes).toContain('packing');
    expect(stepCodes).toContain('shipping');
    expect(stepCodes).toContain('milling_wet');
  });

  it('groups teeth sharing a prosthesis in one detail, and keeps other prosthesis separate', async () => {
    const pfmSteps = stepsFromSeed('pfm_crown');
    const zirconiaSteps = stepsFromSeed('monolithic_zirconia');
    const { tx, created } = buildMockTx({
      toothProsthesisRows: [
        { treatmentDetailId: 'detail-1', tooth: '15', prosthesisTypeCode: 'pfm_crown' },
        { treatmentDetailId: 'detail-1', tooth: '14', prosthesisTypeCode: 'pfm_crown' },
        { treatmentDetailId: 'detail-1', tooth: '16', prosthesisTypeCode: 'monolithic_zirconia' },
      ],
      prosthesisTypes: [
        { code: 'pfm_crown', steps: pfmSteps },
        { code: 'monolithic_zirconia', steps: zirconiaSteps },
      ],
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-case-group', 'en');

    expect(count).toBe(pfmSteps.length + zirconiaSteps.length);
    const rows = created as Array<{ teeth: string[]; prosthesisTypeCode: string }>;
    const pfmRows = rows.filter((r) => r.prosthesisTypeCode === 'pfm_crown');
    const zirconiaRows = rows.filter((r) => r.prosthesisTypeCode === 'monolithic_zirconia');

    expect(pfmRows).toHaveLength(pfmSteps.length);
    expect(zirconiaRows).toHaveLength(zirconiaSteps.length);
    // Teeth sharing the prosthesis in the same detail are merged and sorted.
    expect(pfmRows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['14', '15']))).toBe(true);
    expect(zirconiaRows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['16']))).toBe(true);
  });

  it('omits packing and shipping for smile_design', async () => {
    const smileSteps = stepsFromSeed('smile_design');
    const { tx, created } = buildMockTx({
      toothProsthesisRows: [
        {
          treatmentDetailId: 'detail-1',
          tooth: '11',
          prosthesisTypeCode: 'smile_design',
        },
      ],
      prosthesisTypes: [{ code: 'smile_design', steps: smileSteps }],
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-case-2', 'en');

    expect(count).toBe(smileSteps.length);
    const stepCodes = (created as Array<{ workflowStepCode: string }>).map(
      (row) => row.workflowStepCode,
    );
    expect(stepCodes).not.toContain('packing');
    expect(stepCodes).not.toContain('shipping');
    expect(stepCodes).toContain('printer_resin');
  });

  it('merges teeth with the same prosthesis type across selection groups', async () => {
    const pfmSteps = stepsFromSeed('pfm_crown');
    const { tx, created } = buildMockTx({
      toothProsthesisRows: [
        {
          treatmentDetailId: 'detail-1',
          tooth: '14',
          prosthesisTypeCode: 'pfm_crown',
          selectionGroupId: 'connected-1',
        },
        {
          treatmentDetailId: 'detail-1',
          tooth: '15',
          prosthesisTypeCode: 'pfm_crown',
          selectionGroupId: 'connected-1',
        },
        {
          treatmentDetailId: 'detail-1',
          tooth: '21',
          prosthesisTypeCode: 'pfm_crown',
          selectionGroupId: 'single-21',
        },
      ],
      prosthesisTypes: [{ code: 'pfm_crown', steps: pfmSteps }],
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-case-merge', 'en');
    expect(count).toBe(pfmSteps.length);
    const rows = created as Array<{ teeth: string[]; prosthesisTypeCode: string }>;
    expect(rows).toHaveLength(pfmSteps.length);
    expect(rows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['14', '15', '21']))).toBe(
      true,
    );
  });

  it('skips generation when tasks already exist', async () => {
    const { tx } = buildMockTx({
      existingCount: 3,
      toothProsthesisRows: [],
      prosthesisTypes: [],
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-case-3', 'en');

    expect(count).toBe(0);
    expect(tx.labCaseTask.createMany).not.toHaveBeenCalled();
  });

  it('creates tasks from lab-origin lines using sourceKey and lineId', async () => {
    const pfmSteps = stepsFromSeed('pfm_crown');
    const { tx, created } = buildMockTx({
      toothProsthesisRows: [
        {
          lineId: 'line-1',
          sourceKey: 'line-1',
          tooth: '11',
          prosthesisTypeCode: 'pfm_crown',
        },
        {
          lineId: 'line-1',
          sourceKey: 'line-1',
          tooth: '21',
          prosthesisTypeCode: 'pfm_crown',
        },
      ],
      prosthesisTypes: [{ code: 'pfm_crown', steps: pfmSteps }],
    });

    const count = await generateLabCaseTasks(tx as never, 'lab-internal-1', 'en');
    expect(count).toBe(pfmSteps.length);
    expect(created[0]).toMatchObject({
      lineId: 'line-1',
      sourceKey: 'line-1',
      treatmentDetailId: null,
      teeth: ['11', '21'],
    });
  });
});
