import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import PatientSearch from '@/components/PatientSearch.vue'

vi.mock('@/api/client', () => ({
  get: vi.fn(),
}))

import { get } from '@/api/client'

const mockedGet = vi.mocked(get)

beforeEach(() => {
  vi.clearAllMocks()
  vi.useFakeTimers()
})

describe('PatientSearch', () => {
  it('renders a search input', () => {
    const wrapper = mount(PatientSearch)
    const input = wrapper.find('input')
    expect(input.exists()).toBe(true)
    expect(input.attributes('placeholder')).toContain('Search')
  })

  it('does not search when query is less than 2 characters', async () => {
    const wrapper = mount(PatientSearch)
    const input = wrapper.find('input')

    await input.setValue('J')
    await input.trigger('input')
    vi.advanceTimersByTime(400)

    expect(mockedGet).not.toHaveBeenCalled()
  })

  it('searches after debounce when query is 2+ characters', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [{ id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' }],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    const input = wrapper.find('input')

    await input.setValue('Jane')
    await input.trigger('input')

    expect(mockedGet).not.toHaveBeenCalled()

    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()

    expect(mockedGet).toHaveBeenCalledWith('patients/search', { q: 'Jane' })
  })

  it('shows results dropdown after search', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [
        { id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' },
        { id: 'p2', fullName: 'Jane Smith', mrn: 'MRN-002' },
      ],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    await wrapper.find('input').setValue('Jane')
    await wrapper.find('input').trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    const items = wrapper.findAll('li')
    expect(items.length).toBe(2)
    expect(items[0].text()).toContain('Jane Doe')
    expect(items[0].text()).toContain('MRN-001')
  })

  it('emits update:modelValue on patient selection', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [{ id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' }],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    await wrapper.find('input').setValue('Jane')
    await wrapper.find('input').trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    await wrapper.find('li').trigger('click')

    expect(wrapper.emitted('update:modelValue')).toBeTruthy()
    expect(wrapper.emitted('update:modelValue')![0]).toEqual(['p1'])
  })

  it('shows selected patient name after selection', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [{ id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' }],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    await wrapper.find('input').setValue('Jane')
    await wrapper.find('input').trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    await wrapper.find('li').trigger('click')
    await wrapper.vm.$nextTick()

    expect(wrapper.text()).toContain('Selected: Jane Doe')
  })

  it('clears results list on patient selection', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [{ id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' }],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    await wrapper.find('input').setValue('Jane')
    await wrapper.find('input').trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    await wrapper.find('li').trigger('click')
    await wrapper.vm.$nextTick()

    expect(wrapper.findAll('li').length).toBe(0)
  })

  it('sets input value to patient name on selection', async () => {
    mockedGet.mockResolvedValueOnce({
      success: true,
      statusCode: 200,
      data: [{ id: 'p1', fullName: 'Jane Doe', mrn: 'MRN-001' }],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    const input = wrapper.find('input')
    await input.setValue('Jane')
    await input.trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    await wrapper.find('li').trigger('click')
    await wrapper.vm.$nextTick()

    expect(input.element.value).toBe('Jane Doe')
  })

  it('clears results on API error', async () => {
    mockedGet.mockRejectedValueOnce(new Error('Network error'))

    const wrapper = mount(PatientSearch)
    await wrapper.find('input').setValue('Jane')
    await wrapper.find('input').trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()
    await wrapper.vm.$nextTick()

    expect(wrapper.findAll('li').length).toBe(0)
  })

  it('debounces multiple rapid inputs', async () => {
    mockedGet.mockResolvedValue({
      success: true,
      statusCode: 200,
      data: [],
      error: null,
    })

    const wrapper = mount(PatientSearch)
    const input = wrapper.find('input')

    await input.setValue('Ja')
    await input.trigger('input')
    vi.advanceTimersByTime(100)

    await input.setValue('Jan')
    await input.trigger('input')
    vi.advanceTimersByTime(100)

    await input.setValue('Jane')
    await input.trigger('input')
    vi.advanceTimersByTime(300)
    await vi.runAllTimersAsync()

    expect(mockedGet).toHaveBeenCalledTimes(1)
    expect(mockedGet).toHaveBeenCalledWith('patients/search', { q: 'Jane' })
  })
})
