import { OctoverService } from './octover.service';
import { encodeOctoverSession } from './octover.types';

/**
 * octover 자동차보험 연동(별도 AB001 호출) 분기 검증.
 *  - 건강/자동차 모두 성공 / 자동차 자료없음 / 자동차 실패 시 건강 성공 유지 / 자동차 미요청 / 건강 실패.
 * http.post 를 목으로 옥토버 응답을 시뮬레이션한다(실호출 X).
 */
describe('OctoverService.fetchTreatmentMultiple (자동차 연동)', () => {
  let svc: OctoverService;
  let post: jest.Mock;

  beforeEach(() => {
    post = jest.fn();
    const config = {
      get: (k: string) =>
        (({ 'octover.apiKey': 'KEY', 'octover.baseUrl': 'http://octover.test' }) as Record<string, string>)[k],
    } as any;
    const http = { post } as any;
    const repo = { saveTest: jest.fn().mockReturnValue(undefined) } as any;
    svc = new OctoverService(config, http, repo);
  });

  const jti = encodeOctoverSession({ encInput: 'ENC', authType: 'simple' });
  const item = (inquiryType: string) =>
    ({ inquiryType, type: '1', startDate: '20200101', endDate: '20250101', twoWayInfo: { jti } }) as any;

  const basicRow = {
    diagnosisStartDate: '20230101', hospitalName: 'A의원', department: '내과', type: '외래',
    mainDiseaseCode: 'J00', mainDiseaseName: '감기', visitDateCount: '1',
    totalCharge: '10000', discountCharge: '7000', selfCharge: '3000', hospitalCode: 'H1',
  };
  const detailRow = {
    diagnosisStartDate: '20230101', hospitalName: 'A의원', diagnosisData: '진찰',
    codeName: '초진', oneDose: '1', oneDoseCount: '1', totalDosingdays: '1',
  };
  const abData = () => ({
    name: '홍길동', inqrDtStrt: '20200101', inqrDtEnd: '20250101',
    basicArray: [basicRow], detailArray: [detailRow], pharmacyArray: [],
  });

  //* 옥토버 응답 봉투: 로그인결과(성공) + AB001(코드/데이터)
  const resp = (abCode: string, ab?: any, header: string | null = 'CONN-1') => ({
    out: {
      code: '1000200', msg: '',
      ...(header === null ? {} : { header }),
      data: {
        simpleAuthLoginResult: { code: '1000200', msg: '' },
        AB001: { code: abCode, msg: '', data: ab },
      },
    },
  });
  const carResp = (abCode: string, ab?: any) => ({
    out: { code: '1000200', msg: '', data: { AB001: { code: abCode, msg: '', data: ab } } },
  });

  it('건강+자동차 모두 성공 → data 2건, 둘 다 매핑, 호출 2회', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(carResp('1000200', abData()));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(2);
    expect(r.data[0].resBasicTreatList).toHaveLength(1);
    expect(r.data[1].resBasicTreatList).toHaveLength(1);
    expect(r.data[1].resPrescribeDrugList).toHaveLength(0); //* 자동차는 처방 없음
    expect(post).toHaveBeenCalledTimes(2);
    expect(post.mock.calls[0][1].in).toMatchObject({ connReq: 'true', connHeader: '' });
    expect(post.mock.calls[0][1].jobs.methods).toEqual(['simpleAuthLoginResult', 'AB001']);
    expect(post.mock.calls[1][1].in).toMatchObject({ connReq: 'true', connHeader: 'CONN-1' });
    expect(post.mock.calls[1][1].in.simpleAuthLoginResult).toBeUndefined();
    expect(post.mock.calls[1][1].jobs.methods).toEqual(['AB001']);
  });

  it('자동차 전용 필드(진료일수/자동차보험총진료비)가 채워진다 (분석 0 방지)', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000200', abData()));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    const car = r.data[1].resBasicTreatList[0] as any;
    //* createCarTreat→분석이 읽는 자동차 전용 필드
    expect(car.resTreatDate).toBe('1'); //* visitDateCount → 진료일수
    expect(car.resMedicalFee).toBe('7000'); //* discountCharge → 자동차보험 총진료비(근사)
    expect(car.resTotalAmount).toBe('10000'); //* totalCharge → 청구진료비 총금액
    //* 건강 index 0 은 자동차 전용필드 미설정(건강 매퍼)
    const health = r.data[0].resBasicTreatList[0] as any;
    expect(health.resMedicalFee).toBeUndefined();
  });

  it('자동차 자료없음(1000204) → 성공, 자동차 빈값', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000204', undefined));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(2);
    expect(r.data[1].resBasicTreatList).toHaveLength(0);
  });

  it('자동차 수집실패(1000300)여도 건강 성공은 유지한다', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000300', undefined));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(1);
    expect(post).toHaveBeenCalledTimes(2);
  });

  it('자동차 미요청 → AB001 1회만, data 1건', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData()));
    const r = await svc.fetchTreatmentMultiple([item('0')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(1);
    expect(post).toHaveBeenCalledTimes(1);
    expect(post.mock.calls[0][1].in.connReq).toBeUndefined();
    expect(post.mock.calls[0][1].in.connHeader).toBeUndefined();
  });

  it('건강 수집실패 → 전체 실패, 자동차 호출 안 함', async () => {
    post.mockResolvedValueOnce(resp('1000300', undefined));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).not.toBe('CF-00000');
    expect(post).toHaveBeenCalledTimes(1);
  });

  it('건강 자료없음(1000204) → 성공, 건강 빈값', async () => {
    post.mockResolvedValueOnce(resp('1000204', undefined)).mockResolvedValueOnce(resp('1000204', undefined));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data[0].resBasicTreatList).toHaveLength(0);
  });

  //* ── 부분성공 코드(정상등급) ──
  it('자동차 부분성공(1000206) → 성공, 매핑됨', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000206', abData()));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data[1].resBasicTreatList).toHaveLength(1);
  });

  it('자동차 마지막거래(1000210) → 성공, 매핑됨', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000210', abData()));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data[1].resBasicTreatList).toHaveLength(1);
  });

  //* ── 자동차 실패는 건강 성공 보존 ──
  it('자동차 세션 오류여도 건강 성공은 유지한다', async () => {
    const healthOk = resp('1000200', abData());
    const carLoginFail = {
      out: { code: '1000101', msg: '로그인 이후 이용 가능', data: { simpleAuthLoginResult: { code: '1000101', msg: '' }, AB001: { code: '1000101', msg: '' } } },
    };
    post.mockResolvedValueOnce(healthOk).mockResolvedValueOnce(carLoginFail);
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(1);
    expect(post).toHaveBeenCalledTimes(2);
  });

  it('건강 응답에 세션 header가 없으면 자동차를 호출하지 않는다', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData(), null));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(1);
    expect(post).toHaveBeenCalledTimes(1);
  });

  it('자동차 호출 예외여도 건강 성공은 유지한다', async () => {
    post.mockResolvedValueOnce(resp('1000200', abData())).mockRejectedValueOnce(new Error('timeout'));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(1);
  });

  //* ── 입력 방어 ──
  it('빈 요청(items=[]) → throw', async () => {
    await expect(svc.fetchTreatmentMultiple([], 1)).rejects.toThrow();
    expect(post).not.toHaveBeenCalled();
  });

  it('세션(jti) 복원 실패 → CF-12000, post 미호출', async () => {
    const bad = { inquiryType: '0', type: '1', startDate: '20200101', endDate: '20250101', twoWayInfo: { jti: '!!not-base64!!' } } as any;
    const r = await svc.fetchTreatmentMultiple([bad], 1);
    expect(r.result.code).toBe('CF-12000');
    expect(post).not.toHaveBeenCalled();
  });

  //* ── 응답 구조 변형: 자동차 basic만 있고 detail 없음(TreatService 부분허용이 흡수) ──
  it('자동차 basicArray만(detail 없음) → octover는 정상 반환(detail 빈)', async () => {
    const onlyBasic = { name: 'x', inqrDtStrt: '', inqrDtEnd: '', basicArray: [basicRow], detailArray: [], pharmacyArray: [] };
    post.mockResolvedValueOnce(resp('1000200', abData())).mockResolvedValueOnce(resp('1000200', onlyBasic));
    const r = await svc.fetchTreatmentMultiple([item('0'), item('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data[1].resBasicTreatList).toHaveLength(1);
    expect(r.data[1].resDetailTreatList).toHaveLength(0);
  });

  //* ── mobile(SMS) 인증 경로 ──
  it('mobile 인증 + 건강/자동차 성공 → data 2건', async () => {
    const jtiM = encodeOctoverSession({ encInput: 'ENC', authType: 'mobile' });
    const itemM = (inq: string) =>
      ({ inquiryType: inq, type: '1', startDate: '20200101', endDate: '20250101', smsAuthNo: '123456', twoWayInfo: { jti: jtiM } }) as any;
    const respM = (abCode: string, ab?: any) => ({
      out: { code: '1000200', msg: '', header: 'CONN-M', data: { phoneVerifyResult: { code: '1000200', msg: '' }, AB001: { code: abCode, msg: '', data: ab } } },
    });
    post.mockResolvedValueOnce(respM('1000200', abData())).mockResolvedValueOnce(respM('1000200', abData()));
    const r = await svc.fetchTreatmentMultiple([itemM('0'), itemM('1')], 1);
    expect(r.result.code).toBe('CF-00000');
    expect(r.data).toHaveLength(2);
    expect(post.mock.calls[1][1].jobs.methods).toEqual(['AB001']);
  });
});
