import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useAnalysisStore } from './store'

vi.mock('../../shared/api/http', () => ({
  apiFetch: vi.fn(),
}))

vi.mock('./api', () => ({
  fetchAnalyses: vi.fn(),
  fetchAnalysis: vi.fn(),
  createAnalysis: vi.fn(),
  deleteAnalysis: vi.fn(),
}))

import * as api from './api'

// ---------------------------------------------------------------------------
// API layer — body construction with pipeline options
// ---------------------------------------------------------------------------

describe('createAnalysis — pipeline options body construction', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // Reset the real module to get the unmocked version
    vi.doUnmock('./api')
  })

  // We test the mock integration (store → api) separately below.
  // Here we verify the raw apiFetch call shape.

  it('sends body without pipelineOptions when null', async () => {
    const { apiFetch: realApiFetch } = await import('../../shared/api/http')
    realApiFetch.mockResolvedValue({ id: '1', status: 'PENDING' })

    const { createAnalysis: real } = await import('./api')
    await real('doc-1', null)

    expect(realApiFetch).toHaveBeenCalledWith('/api/analyses', {
      method: 'POST',
      body: JSON.stringify({ documentId: 'doc-1' }),
    })
  })

  it('sends body without pipelineOptions when undefined', async () => {
    const { apiFetch: realApiFetch } = await import('../../shared/api/http')
    realApiFetch.mockResolvedValue({ id: '1', status: 'PENDING' })

    const { createAnalysis: real } = await import('./api')
    await real('doc-1')

    expect(realApiFetch).toHaveBeenCalledWith('/api/analyses', {
      method: 'POST',
      body: JSON.stringify({ documentId: 'doc-1' }),
    })
  })

  it('includes pipelineOptions in body when provided', async () => {
    const { apiFetch: realApiFetch } = await import('../../shared/api/http')
    realApiFetch.mockResolvedValue({ id: '1', status: 'PENDING' })

    const opts = {
      do_ocr: false,
      do_table_structure: true,
      table_mode: 'fast',
      do_code_enrichment: true,
      do_formula_enrichment: false,
      do_picture_classification: false,
      do_picture_description: false,
      generate_picture_images: false,
      generate_page_images: false,
      images_scale: 1.0,
    }

    const { createAnalysis: real } = await import('./api')
    await real('doc-1', opts)

    const sentBody = JSON.parse(realApiFetch.mock.calls[0][1].body)
    expect(sentBody.documentId).toBe('doc-1')
    expect(sentBody.pipelineOptions).toEqual(opts)
  })

  it('includes only specified fields in partial options', async () => {
    const { apiFetch: realApiFetch } = await import('../../shared/api/http')
    realApiFetch.mockResolvedValue({ id: '1', status: 'PENDING' })

    const opts = { do_ocr: false }

    const { createAnalysis: real } = await import('./api')
    await real('doc-1', opts)

    const sentBody = JSON.parse(realApiFetch.mock.calls[0][1].body)
    expect(sentBody.pipelineOptions).toEqual({ do_ocr: false })
  })

  it('does not include pipelineOptions key when options is empty object treated as falsy', async () => {
    const { apiFetch: realApiFetch } = await import('../../shared/api/http')
    realApiFetch.mockResolvedValue({ id: '1', status: 'PENDING' })

    // Empty object is truthy in JS, so it SHOULD be included
    const { createAnalysis: real } = await import('./api')
    await real('doc-1', {})

    const sentBody = JSON.parse(realApiFetch.mock.calls[0][1].body)
    expect(sentBody.pipelineOptions).toEqual({})
  })
})

// ---------------------------------------------------------------------------
// Store → API integration — pipeline options forwarding
// ---------------------------------------------------------------------------

describe('useAnalysisStore — pipeline options forwarding', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    vi.clearAllMocks()
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  it('run() passes null when no options provided', async () => {
    const job = { id: 'j1', status: 'PENDING', documentId: 'd1' }
    api.createAnalysis.mockResolvedValue(job)
    api.fetchAnalysis.mockResolvedValue({ ...job, status: 'COMPLETED' })

    const store = useAnalysisStore()
    await store.run('d1')

    expect(api.createAnalysis).toHaveBeenCalledWith('d1', null, null)
    store.stopPolling()
  })

  it('run() forwards full pipeline options object', async () => {
    const job = { id: 'j1', status: 'PENDING', documentId: 'd1' }
    api.createAnalysis.mockResolvedValue(job)
    api.fetchAnalysis.mockResolvedValue({ ...job, status: 'COMPLETED' })

    const store = useAnalysisStore()
    const opts = {
      do_ocr: false,
      do_table_structure: true,
      table_mode: 'fast',
      do_code_enrichment: true,
      do_formula_enrichment: false,
      do_picture_classification: false,
      do_picture_description: true,
      generate_picture_images: true,
      generate_page_images: false,
      images_scale: 2.0,
    }
    await store.run('d1', opts)

    expect(api.createAnalysis).toHaveBeenCalledWith('d1', opts, null)
    store.stopPolling()
  })

  it('run() forwards partial pipeline options', async () => {
    const job = { id: 'j1', status: 'PENDING', documentId: 'd1' }
    api.createAnalysis.mockResolvedValue(job)
    api.fetchAnalysis.mockResolvedValue({ ...job, status: 'COMPLETED' })

    const store = useAnalysisStore()
    const opts = { do_ocr: false }
    await store.run('d1', opts)

    expect(api.createAnalysis).toHaveBeenCalledWith('d1', { do_ocr: false }, null)
    store.stopPolling()
  })

  it('run() sets running state even with pipeline options', async () => {
    const job = { id: 'j1', status: 'PENDING', documentId: 'd1' }
    api.createAnalysis.mockResolvedValue(job)

    const store = useAnalysisStore()
    const opts = { do_ocr: false, table_mode: 'fast' }
    await store.run('d1', opts)

    expect(store.running).toBe(true)
    expect(store.currentAnalysis).toEqual(job)
    store.stopPolling()
  })

  it('run() with options still triggers polling', async () => {
    const job = { id: 'j1', status: 'PENDING', documentId: 'd1' }
    api.createAnalysis.mockResolvedValue(job)
    api.fetchAnalysis.mockResolvedValue({ ...job, status: 'RUNNING' })

    const store = useAnalysisStore()
    await store.run('d1', { do_ocr: false })

    // After 2s polling should fire
    await vi.advanceTimersByTimeAsync(2000)
    expect(api.fetchAnalysis).toHaveBeenCalledWith('j1')

    // Still running
    expect(store.running).toBe(true)

    // Now complete
    api.fetchAnalysis.mockResolvedValue({ ...job, status: 'COMPLETED' })
    await vi.advanceTimersByTimeAsync(2000)
    expect(store.running).toBe(false)

    store.stopPolling()
  })

  it('run() with options handles API error gracefully', async () => {
    api.createAnalysis.mockRejectedValue(new Error('Server error'))
    vi.spyOn(console, 'error').mockImplementation(() => {})

    const store = useAnalysisStore()
    await expect(store.run('d1', { do_ocr: false })).rejects.toThrow('Server error')

    expect(store.running).toBe(false)
    expect(store.currentAnalysis).toBeNull()
  })
})
