import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useHealthStore } from './health';

vi.mock('../api/health/patients', () => ({
  patientApi: {
    get: vi.fn(),
  },
}));

vi.mock('../api/health/doctors', () => ({
  doctorApi: {
    get: vi.fn(),
  },
}));

import { patientApi } from '../api/health/patients';
import { doctorApi } from '../api/health/doctors';

const mockedPatientGet = vi.mocked(patientApi.get);
const mockedDoctorGet = vi.mocked(doctorApi.get);

describe('useHealthStore', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useHealthStore.setState({
      patientNames: {},
      doctorNames: {},
      loadingIds: new Set(),
    });
  });

  describe('initial state', () => {
    it('starts with empty caches', () => {
      const s = useHealthStore.getState();
      expect(s.patientNames).toEqual({});
      expect(s.doctorNames).toEqual({});
      expect(s.loadingIds.size).toBe(0);
    });
  });

  describe('getPatientName / getDoctorName', () => {
    it('returns truncated id when name not cached', () => {
      expect(useHealthStore.getState().getPatientName('abcdefgh-1234')).toBe('abcdefgh');
      expect(useHealthStore.getState().getDoctorName('xyz98765-4321')).toBe('xyz98765');
    });

    it('returns cached name when available', () => {
      useHealthStore.setState({ patientNames: { id1: 'Alice' }, doctorNames: { id2: 'Dr.Bob' } });
      expect(useHealthStore.getState().getPatientName('id1')).toBe('Alice');
      expect(useHealthStore.getState().getDoctorName('id2')).toBe('Dr.Bob');
    });
  });

  describe('resolvePatientName', () => {
    it('returns cached name without API call', async () => {
      useHealthStore.setState({ patientNames: { pid1: '张三' } });
      const name = await useHealthStore.getState().resolvePatientName('pid1');
      expect(name).toBe('张三');
      expect(mockedPatientGet).not.toHaveBeenCalled();
    });

    it('fetches and caches name on success', async () => {
      mockedPatientGet.mockResolvedValueOnce({ id: 'p1', name: '李四' } as any);
      const name = await useHealthStore.getState().resolvePatientName('p1');
      expect(name).toBe('李四');
      expect(useHealthStore.getState().patientNames['p1']).toBe('李四');
    });

    it('falls back to truncated id on API failure', async () => {
      mockedPatientGet.mockRejectedValueOnce(new Error('not found'));
      const name = await useHealthStore.getState().resolvePatientName('abcdefgh-xxx');
      expect(name).toBe('abcdefgh');
      expect(useHealthStore.getState().patientNames['abcdefgh-xxx']).toBe('abcdefgh');
    });

    it('deduplicates concurrent calls for same id', async () => {
      let resolve: (v: any) => void;
      const promise = new Promise((r) => { resolve = r; });
      mockedPatientGet.mockReturnValueOnce(promise as any);

      const r1 = useHealthStore.getState().resolvePatientName('dup');
      const r2 = useHealthStore.getState().resolvePatientName('dup');

      resolve!({ id: 'dup', name: '王五' } as any);
      const [n1, n2] = await Promise.all([r1, r2]);
      expect(n1).toBe('王五');
      expect(typeof n2).toBe('string');
      expect(mockedPatientGet).toHaveBeenCalledTimes(1);
    });
  });

  describe('resolveDoctorName', () => {
    it('returns cached name without API call', async () => {
      useHealthStore.setState({ doctorNames: { did1: '赵医生' } });
      const name = await useHealthStore.getState().resolveDoctorName('did1');
      expect(name).toBe('赵医生');
      expect(mockedDoctorGet).not.toHaveBeenCalled();
    });

    it('fetches and caches name on success', async () => {
      mockedDoctorGet.mockResolvedValueOnce({ id: 'd1', name: '孙医生' } as any);
      const name = await useHealthStore.getState().resolveDoctorName('d1');
      expect(name).toBe('孙医生');
      expect(useHealthStore.getState().doctorNames['d1']).toBe('孙医生');
    });

    it('falls back to truncated id on API failure', async () => {
      mockedDoctorGet.mockRejectedValueOnce(new Error('not found'));
      const name = await useHealthStore.getState().resolveDoctorName('doctor123-abc');
      expect(name).toBe('doctor12');
    });
  });

  describe('batchResolvePatientNames', () => {
    it('skips already cached ids', async () => {
      useHealthStore.setState({ patientNames: { p1: 'cached' } });
      await useHealthStore.getState().batchResolvePatientNames(['p1']);
      expect(mockedPatientGet).not.toHaveBeenCalled();
    });

    it('resolves multiple uncached ids', async () => {
      mockedPatientGet
        .mockResolvedValueOnce({ id: 'b1', name: 'batch1' } as any)
        .mockResolvedValueOnce({ id: 'b2', name: 'batch2' } as any);

      await useHealthStore.getState().batchResolvePatientNames(['b1', 'b2']);
      expect(useHealthStore.getState().patientNames['b1']).toBe('batch1');
      expect(useHealthStore.getState().patientNames['b2']).toBe('batch2');
    });

    it('handles partial failures gracefully', async () => {
      mockedPatientGet
        .mockResolvedValueOnce({ id: 'ok', name: '成功' } as any)
        .mockRejectedValueOnce(new Error('fail'));

      await useHealthStore.getState().batchResolvePatientNames(['ok', 'failid123456']);
      expect(useHealthStore.getState().patientNames['ok']).toBe('成功');
      expect(useHealthStore.getState().patientNames['failid123456']).toBe('failid12');
    });

    it('deduplicates input ids', async () => {
      mockedPatientGet.mockResolvedValue({ id: 'dup', name: '去重' } as any);
      await useHealthStore.getState().batchResolvePatientNames(['dup', 'dup', 'dup']);
      expect(mockedPatientGet).toHaveBeenCalledTimes(1);
    });
  });

  describe('batchResolveDoctorNames', () => {
    it('skips already cached ids', async () => {
      useHealthStore.setState({ doctorNames: { d1: 'cached' } });
      await useHealthStore.getState().batchResolveDoctorNames(['d1']);
      expect(mockedDoctorGet).not.toHaveBeenCalled();
    });

    it('resolves multiple uncached ids', async () => {
      mockedDoctorGet
        .mockResolvedValueOnce({ id: 'bd1', name: '陈医生' } as any)
        .mockResolvedValueOnce({ id: 'bd2', name: '周医生' } as any);

      await useHealthStore.getState().batchResolveDoctorNames(['bd1', 'bd2']);
      expect(useHealthStore.getState().doctorNames['bd1']).toBe('陈医生');
      expect(useHealthStore.getState().doctorNames['bd2']).toBe('周医生');
    });

    it('handles partial failures gracefully', async () => {
      mockedDoctorGet
        .mockResolvedValueOnce({ id: 'ok', name: '成功' } as any)
        .mockRejectedValueOnce(new Error('fail'));

      await useHealthStore.getState().batchResolveDoctorNames(['ok', 'failid123456']);
      expect(useHealthStore.getState().doctorNames['ok']).toBe('成功');
      expect(useHealthStore.getState().doctorNames['failid123456']).toBe('failid12');
    });
  });
});
