import {
  Dosing30DayGroupInput,
  buildAnalyzeRequest,
  serializeAnalyzeRequest,
  toPrescriptionRecords,
} from './ddx-payload';

//* 사례 축은 픽스처 `.claude/plans/drug-diag-api-m1.fixture.json` 의 boundary_index 와 같다 —
//* 수량 결측 · 문자열 수량 · 빈 진단 배열 · 약품명 결측. 매퍼 축의 유일한 실행 검사다(관측 1).

const group = (over: Partial<Dosing30DayGroupInput> = {}): Dosing30DayGroupInput => ({
  categoryName: '당뇨병용제',
  totalDosingDays: 360,
  startDate: '2025-01-15',
  endDate: '2025-06-10',
  records: [],
  ...over,
});

const record = (over: Partial<Dosing30DayGroupInput['records'][number]> = {}) => ({
  efficacy: '당뇨병용제',
  treatStartDate: '2025-01-15',
  hospitalName: '예시내과의원',
  diagnoses: [{ diseaseCode: 'E119', diseaseName: '2형 당뇨병' }],
  drugName: '글루코파지엑스알1000밀리그램서방정',
  ingredients: '메트포르민염산염',
  oneDose: 1,
  dailyDosesNumber: 2,
  totalDosingDays: 90,
  ...over,
});

describe('ddx-payload.toPrescriptionRecords', () => {
  it('정상 행을 스펙 필드로 옮기고 그룹 값을 drug_class·class_total_days 에 싣는다', () => {
    const [out] = toPrescriptionRecords([group({ records: [record()] })]);

    expect(out.drug_class).toBe('당뇨병용제');
    expect(out.class_total_days).toBe(360);
    expect(out.treatment_start_date).toBe('2025-01-15');
    expect(out.provider).toBe('예시내과의원');
    expect(out.diagnosis_codes).toEqual(['E119']);
    expect(out.diagnosis_names).toEqual(['2형 당뇨병']);
    expect(out.days_supplied).toBe(90);
    expect(out.dose_per_administration).toBe(1);
    expect(out.administrations_per_day).toBe(2);
  });

  //* 수량 4필드 전건 × 강제 축. 한 필드만 보면 나머지 셋의 회귀를 놓친다.
  const quantityAxis: Array<[string, unknown, number | null]> = [
    ['정수', 90, 90],
    ['0', 0, 0],
    ['정수형 문자열', '90', 90],
    ['결측(null)', null, null],
    ['단위 붙은 문자열', '90일', null],
    ['소수', 1.5, null],
    ['음수', -1, null],
    ['빈 문자열', '', null],
  ];

  it.each(quantityAxis)('days_supplied — %s 는 %p → %p', (_label, input, want) => {
    const [out] = toPrescriptionRecords([
      group({ records: [record({ totalDosingDays: input as number | null })] }),
    ]);
    expect(out.days_supplied).toBe(want);
  });

  it.each(quantityAxis)('dose_per_administration — %s 는 %p → %p', (_label, input, want) => {
    const [out] = toPrescriptionRecords([
      group({ records: [record({ oneDose: input as number | null })] }),
    ]);
    expect(out.dose_per_administration).toBe(want);
  });

  it.each(quantityAxis)('administrations_per_day — %s 는 %p → %p', (_label, input, want) => {
    const [out] = toPrescriptionRecords([
      group({ records: [record({ dailyDosesNumber: input as number | null })] }),
    ]);
    expect(out.administrations_per_day).toBe(want);
  });

  it.each(quantityAxis)('class_total_days — %s 는 %p → %p', (_label, input, want) => {
    const [out] = toPrescriptionRecords([
      group({ totalDosingDays: input as number, records: [record()] }),
    ]);
    expect(out.class_total_days).toBe(want);
  });

  it('약품명이 없는 행은 산출에서 제외한다 (스펙 required · minLength 1)', () => {
    const out = toPrescriptionRecords([
      group({ records: [record(), record({ drugName: null }), record({ drugName: '   ' })] }),
    ]);
    expect(out).toHaveLength(1);
  });

  it('빈 진단 배열은 빈 배열 두 개로 나가고 키를 지우지 않는다', () => {
    const [out] = toPrescriptionRecords([group({ records: [record({ diagnoses: [] })] })]);
    expect(out.diagnosis_codes).toEqual([]);
    expect(out.diagnosis_names).toEqual([]);
  });

  it('코드·명이 둘 다 빈 진단 원소는 버리고, 한쪽만 있는 원소는 빈 문자열로 짝을 맞춘다', () => {
    const [out] = toPrescriptionRecords([
      group({
        records: [
          record({
            diagnoses: [
              { diseaseCode: null, diseaseName: null },
              { diseaseCode: 'M170', diseaseName: null },
            ],
          }),
        ],
      }),
    ]);
    expect(out.diagnosis_codes).toEqual(['M170']);
    expect(out.diagnosis_names).toEqual(['']);
  });

  //* `efficacy` 는 허가 효능효과가 아니라 약효 중분류명이므로 indication 자리에 넣지 않는다.
  it('indication 키를 만들지 않는다', () => {
    const [out] = toPrescriptionRecords([group({ records: [record()] })]);
    expect(Object.prototype.hasOwnProperty.call(out, 'indication')).toBe(false);
  });

  //* `#9` 가 바이트 비교를 하므로 키 순서가 계약이다.
  it('키 순서가 결정적이다', () => {
    const [a] = toPrescriptionRecords([group({ records: [record()] })]);
    const [b] = toPrescriptionRecords([
      group({ records: [record({ totalDosingDays: null, diagnoses: [] })] }),
    ]);
    const want = [
      'drug_class',
      'class_total_days',
      'treatment_start_date',
      'provider',
      'diagnosis_codes',
      'diagnosis_names',
      'drug_name',
      'dose_per_administration',
      'administrations_per_day',
      'days_supplied',
    ];
    expect(Object.keys(a)).toEqual(want);
    expect(Object.keys(b)).toEqual(want);
  });

  it('빈 입력과 null 입력에서 빈 배열을 낸다', () => {
    expect(toPrescriptionRecords([])).toEqual([]);
    expect(toPrescriptionRecords(null as unknown as Dosing30DayGroupInput[])).toEqual([]);
  });
});

describe('ddx-payload.buildAnalyzeRequest', () => {
  it('요청 본문의 키 순서와 직렬화가 결정적이다', () => {
    const body = buildAnalyzeRequest({
      personName: '홍길동',
      groups: [group({ records: [record()] })],
      externalRef: 'drug-diag-api-m1-roundtrip',
      store: true,
    });

    expect(Object.keys(body)).toEqual(['person', 'records', 'external_ref', 'store']);
    expect(serializeAnalyzeRequest(body)).toBe(serializeAnalyzeRequest(body));
    expect(serializeAnalyzeRequest(body).endsWith('\n')).toBe(false);
  });
});
