import { describe, it, expect, vi, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useBatchStore } from '@/stores/batches'

vi.mock('@/api/client', () => ({
  get: vi.fn(),
  post: vi.fn(),
  put: vi.fn(),
  del: vi.fn(),
  patch: vi.fn(),
  uploadFile: vi.fn(),
}))

import { get, post, put, del, patch, uploadFile } from '@/api/client'

const mockedGet = vi.mocked(get)
const mockedPost = vi.mocked(post)
const mockedPut = vi.mocked(put)
const mockedDel = vi.mocked(del)
const mockedPatch = vi.mocked(patch)
const mockedUploadFile = vi.mocked(uploadFile)

beforeEach(() => {
  setActivePinia(createPinia())
  vi.clearAllMocks()
})

describe('useBatchStore', () => {
  describe('initial state', () => {
    it('has expected defaults', () => {
      const store = useBatchStore()
      expect(store.batches).toEqual([])
      expect(store.currentBatch).toBeNull()
      expect(store.currentDraft).toBeNull()
      expect(store.totalCount).toBe(0)
      expect(store.loading).toBe(false)
      expect(store.error).toBeNull()
      expect(store.documentUrl).toBeNull()
    })
  })

  describe('listBatches', () => {
    it('populates batches and totalCount on success', async () => {
      const items = [
        { id: 'b1', status: 'UPLOADED', batchType: 'VITALS', track: 'TRACK_A' },
        { id: 'b2', status: 'IN_ENTRY', batchType: 'LABS', track: 'TRACK_A' },
      ]
      mockedGet.mockResolvedValueOnce({
        success: true,
        statusCode: 200,
        data: { items, totalCount: 42, page: 1, pageSize: 20, totalPages: 3 },
        error: null,
      })

      const store = useBatchStore()
      await store.listBatches({ status: 'UPLOADED' })

      expect(store.batches).toEqual(items)
      expect(store.totalCount).toBe(42)
      expect(store.loading).toBe(false)
      expect(store.error).toBeNull()
    })

    it('sets error on failure', async () => {
      mockedGet.mockRejectedValueOnce(new Error('Network error'))

      const store = useBatchStore()
      await store.listBatches({})

      expect(store.error).toBe('Network error')
      expect(store.loading).toBe(false)
    })

    it('sets generic error for non-Error exceptions', async () => {
      mockedGet.mockRejectedValueOnce('string error')

      const store = useBatchStore()
      await store.listBatches({})

      expect(store.error).toBe('Failed to load batches')
    })

    it('sets loading=true during request', async () => {
      let resolvePromise: (v: any) => void
      mockedGet.mockReturnValueOnce(
        new Promise((resolve) => {
          resolvePromise = resolve
        }),
      )

      const store = useBatchStore()
      const promise = store.listBatches({})

      expect(store.loading).toBe(true)

      resolvePromise!({ success: true, statusCode: 200, data: { items: [], totalCount: 0 }, error: null })
      await promise

      expect(store.loading).toBe(false)
    })
  })

  describe('getBatch', () => {
    it('sets currentBatch on success', async () => {
      const batch = { id: 'b1', status: 'UPLOADED', batchType: 'VITALS', documentUrl: 'https://example.com/doc' }
      mockedGet.mockResolvedValueOnce({ success: true, statusCode: 200, data: batch, error: null })

      const store = useBatchStore()
      await store.getBatch('b1')

      expect(store.currentBatch).toEqual(batch)
      expect(store.documentUrl).toBe('https://example.com/doc')
    })

    it('sets error on failure', async () => {
      mockedGet.mockRejectedValueOnce(new Error('Not found'))

      const store = useBatchStore()
      await store.getBatch('bad-id')

      expect(store.error).toBe('Not found')
      expect(store.currentBatch).toBeNull()
    })
  })

  describe('uploadBatch', () => {
    it('returns batch data on success', async () => {
      const batch = { id: 'new-batch', status: 'UPLOADED' }
      mockedUploadFile.mockResolvedValueOnce({ success: true, statusCode: 201, data: batch, error: null })

      const store = useBatchStore()
      const file = new File(['content'], 'scan.pdf', { type: 'application/pdf' })
      const result = await store.uploadBatch(file, 'VITALS', 'TRACK_A')

      expect(result).toEqual(batch)
      expect(mockedUploadFile).toHaveBeenCalledWith('digitization-batches', file, {
        batchType: 'VITALS',
        track: 'TRACK_A',
      })
    })

    it('passes optional patientId and supersedesBatchId', async () => {
      mockedUploadFile.mockResolvedValueOnce({ success: true, statusCode: 201, data: { id: 'b' }, error: null })

      const store = useBatchStore()
      const file = new File(['content'], 'scan.pdf')
      await store.uploadBatch(file, 'VITALS', 'TRACK_A', 'patient-1', 'old-batch')

      expect(mockedUploadFile).toHaveBeenCalledWith('digitization-batches', file, {
        batchType: 'VITALS',
        track: 'TRACK_A',
        patientId: 'patient-1',
        supersedesBatchId: 'old-batch',
      })
    })

    it('passes coverSheetCode when provided', async () => {
      mockedUploadFile.mockResolvedValueOnce({ success: true, statusCode: 201, data: { id: 'b' }, error: null })

      const store = useBatchStore()
      const file = new File(['content'], 'scan.pdf')
      await store.uploadBatch(
        file,
        'VITALS_SHEET',
        'BACKFILL',
        undefined,
        undefined,
        'VCR-CS-A3F7B2D1',
      )

      expect(mockedUploadFile).toHaveBeenCalledWith('digitization-batches', file, {
        batchType: 'VITALS_SHEET',
        track: 'BACKFILL',
        coverSheetCode: 'VCR-CS-A3F7B2D1',
      })
    })

    it('returns null on failure', async () => {
      mockedUploadFile.mockRejectedValueOnce(new Error('Upload failed'))

      const store = useBatchStore()
      const file = new File([''], 'scan.pdf')
      const result = await store.uploadBatch(file, 'VITALS', 'TRACK_A')

      expect(result).toBeNull()
      expect(store.error).toBe('Upload failed')
    })
  })

  describe('assignBatch', () => {
    it('calls PATCH with entryClerkUserId', async () => {
      mockedPatch.mockResolvedValueOnce({ success: true, statusCode: 200, data: {}, error: null })

      const store = useBatchStore()
      await store.assignBatch('b1', 'clerk-42')

      expect(mockedPatch).toHaveBeenCalledWith('digitization-batches/b1/assign', {
        entryClerkUserId: 'clerk-42',
      })
    })

    it('throws on failure', async () => {
      mockedPatch.mockResolvedValueOnce({
        success: false,
        statusCode: 409,
        data: null,
        error: { message: 'Already assigned', code: 'CONFLICT' },
      })

      const store = useBatchStore()
      await expect(store.assignBatch('b1', 'clerk-42')).rejects.toThrow('Already assigned')
    })
  })

  describe('getDraft', () => {
    it('sets currentDraft on success', async () => {
      const draft = {
        patient: { fullName: 'Jane', dateOfBirth: '1990-01-01' },
        encounter: { department: 'ICU' },
        observations: [{ id: 'obs-1', observationCode: 'HEART_RATE', value: 72 }],
      }
      mockedGet.mockResolvedValueOnce({ success: true, statusCode: 200, data: draft, error: null })

      const store = useBatchStore()
      await store.getDraft('b1')

      expect(store.currentDraft).toEqual(draft)
    })
  })

  describe('saveDraftPatient', () => {
    it('calls PUT with patient data', async () => {
      mockedPut.mockResolvedValueOnce({ success: true, statusCode: 200, data: {}, error: null })

      const store = useBatchStore()
      await store.saveDraftPatient('b1', { fullName: 'John Doe' })

      expect(mockedPut).toHaveBeenCalledWith('digitization-batches/b1/draft/patient', {
        fullName: 'John Doe',
        dateOfBirth: null,
        sex: null,
        bloodType: null,
        emergencyContact: null,
        allergies: null,
        noKnownAllergies: false,
        medications: null,
        noActiveMedications: false,
      })
    })
  })

  describe('saveDraftEncounter', () => {
    it('calls PUT with encounter data', async () => {
      mockedPut.mockResolvedValueOnce({ success: true, statusCode: 200, data: {}, error: null })

      const store = useBatchStore()
      await store.saveDraftEncounter('b1', { department: 'ICU' })

      expect(mockedPut).toHaveBeenCalledWith('digitization-batches/b1/draft/encounter', {
        department: 'ICU',
      })
    })
  })

  describe('addObservation', () => {
    it('posts observation and refreshes draft', async () => {
      mockedPost.mockResolvedValueOnce({ success: true, statusCode: 201, data: {}, error: null })
      mockedGet.mockResolvedValueOnce({
        success: true,
        statusCode: 200,
        data: { patient: null, encounter: null, observations: [{ id: 'obs-new' }] },
        error: null,
      })

      const store = useBatchStore()
      await store.addObservation('b1', { observationCode: 'HEART_RATE', value: 72, unit: 'bpm' })

      expect(mockedPost).toHaveBeenCalledWith('digitization-batches/b1/draft/observations', {
        observationCode: 'HEART_RATE',
        value: 72,
        unit: 'bpm',
      })
      expect(mockedGet).toHaveBeenCalledWith('digitization-batches/b1/draft')
    })
  })

  describe('updateObservation', () => {
    it('calls PUT with observation data', async () => {
      mockedPut.mockResolvedValueOnce({ success: true, statusCode: 200, data: {}, error: null })

      const store = useBatchStore()
      await store.updateObservation('b1', 'obs-1', { value: 80 })

      expect(mockedPut).toHaveBeenCalledWith('digitization-batches/b1/draft/observations/obs-1', {
        value: 80,
      })
    })
  })

  describe('deleteObservation', () => {
    it('calls DELETE and refreshes draft', async () => {
      mockedDel.mockResolvedValueOnce({ success: true, statusCode: 200, data: null, error: null })
      mockedGet.mockResolvedValueOnce({
        success: true,
        statusCode: 200,
        data: { patient: null, encounter: null, observations: [] },
        error: null,
      })

      const store = useBatchStore()
      await store.deleteObservation('b1', 'obs-1')

      expect(mockedDel).toHaveBeenCalledWith('digitization-batches/b1/draft/observations/obs-1')
    })
  })

  describe('submitForVerification', () => {
    it('posts to submit endpoint', async () => {
      mockedPost.mockResolvedValueOnce({ success: true, statusCode: 200, data: null, error: null })

      const store = useBatchStore()
      await store.submitForVerification('b1')

      expect(mockedPost).toHaveBeenCalledWith('digitization-batches/b1/submit-for-verification')
    })
  })

  describe('verifyBatch', () => {
    it('posts field checks and pass status', async () => {
      mockedPost.mockResolvedValueOnce({ success: true, statusCode: 200, data: null, error: null })

      const checks = [
        { fieldPath: 'patient.fullName', passed: true },
        { fieldPath: 'patient.dob', passed: true },
      ]
      const store = useBatchStore()
      await store.verifyBatch('b1', checks, true)

      expect(mockedPost).toHaveBeenCalledWith('digitization-batches/b1/verify', {
        fieldChecks: checks,
        passed: true,
      })
    })
  })

  describe('rejectBatch', () => {
    it('posts rejection reason', async () => {
      mockedPost.mockResolvedValueOnce({ success: true, statusCode: 200, data: null, error: null })

      const store = useBatchStore()
      await store.rejectBatch('b1', 'Temperature value appears incorrect')

      expect(mockedPost).toHaveBeenCalledWith('digitization-batches/b1/reject', {
        reason: 'Temperature value appears incorrect',
      })
    })
  })

  describe('approveBatch', () => {
    it('posts with idempotency key and enableRetroactiveAlerts', async () => {
      mockedPost.mockResolvedValueOnce({
        success: true,
        statusCode: 200,
        data: { mrn: 'MRN-001', encounterId: 'enc-1', observationIds: ['o1', 'o2'] },
        error: null,
      })

      const store = useBatchStore()
      const result = await store.approveBatch('b1', true)

      expect(mockedPost).toHaveBeenCalledWith(
        'digitization-batches/b1/approve',
        { enableRetroactiveAlerts: true },
        expect.objectContaining({ 'Idempotency-Key': expect.any(String) }),
      )
      expect(result.data).toEqual({
        mrn: 'MRN-001',
        encounterId: 'enc-1',
        observationIds: ['o1', 'o2'],
      })
    })
  })

  describe('getPatientHistory', () => {
    it('returns history on success', async () => {
      const history = {
        patientId: 'p1',
        totalBatches: 3,
        promotedBatches: 2,
        supersededBatches: 1,
        pendingBatches: 0,
        entries: [],
      }
      mockedGet.mockResolvedValueOnce({ success: true, statusCode: 200, data: history, error: null })

      const store = useBatchStore()
      const result = await store.getPatientHistory('p1')

      expect(result).toEqual(history)
      expect(mockedGet).toHaveBeenCalledWith('patients/p1/digitization-history')
    })

    it('returns null and sets error on failure', async () => {
      mockedGet.mockRejectedValueOnce(new Error('Not found'))

      const store = useBatchStore()
      const result = await store.getPatientHistory('bad')

      expect(result).toBeNull()
      expect(store.error).toBe('Not found')
    })
  })
})
