import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import { useSimulationStore } from '@/stores/simulation'

const {
  fetchSimulationConfig,
  fetchScenarios,
  fetchSessions,
  fetchRuns,
  startRun,
  startSession,
  stopRun,
  fetchDataSummary,
  purgeData,
  loadEncounters,
} = vi.hoisted(() => ({
  fetchSimulationConfig: vi.fn(),
  fetchScenarios: vi.fn(),
  fetchSessions: vi.fn(),
  fetchRuns: vi.fn(),
  startRun: vi.fn(),
  startSession: vi.fn(),
  stopRun: vi.fn(),
  fetchDataSummary: vi.fn().mockResolvedValue({
    simulatedPatients: 0,
    encounters: 0,
    observations: 0,
    alerts: 0,
    activeRuns: 0,
  }),
  purgeData: vi.fn(),
  loadEncounters: vi.fn(),
}))

vi.mock('@/api/simulation', () => ({
  fetchSimulationConfig,
  fetchScenarios,
  fetchSessions,
  fetchRuns,
  startRun,
  startSession,
  stopRun,
  fetchDataSummary,
  purgeData,
  fetchRun: vi.fn(),
}))

vi.mock('@/stores/ward', () => ({
  useWardStore: () => ({ loadEncounters }),
}))

vi.mock('@/stores/alerts', () => ({
  useAlertStore: () => ({ loadGlobalAlerts: vi.fn() }),
}))

function runningRun(overrides = {}) {
  return {
    runId: 'run-1',
    scenarioId: 'uti-sepsis-elderly-01',
    scenarioName: 'UTI Sepsis',
    status: 'RUNNING',
    speed: 60,
    patientId: 'p1',
    encounterId: 'e1',
    patientDisplayName: 'Test Patient',
    startedAt: '2026-08-05T10:00:00Z',
    elapsedRealSeconds: 30,
    lastOffsetMinutes: 20,
    totalOffsetMinutes: 480,
    progressPercent: 4,
    observationsSent: 3,
    medicationsSent: 0,
    ordersPlaced: 0,
    failureReason: null,
    ...overrides,
  }
}

describe('useSimulationStore', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers()
    setActivePinia(createPinia())
    fetchRuns.mockResolvedValue([])
    fetchScenarios.mockResolvedValue([])
    fetchSimulationConfig.mockResolvedValue({
      enabled: true,
      maxSpeed: 600,
      maxConcurrentRuns: 2,
    })
  })

  afterEach(() => {
    const store = useSimulationStore()
    store.stopPolling()
    vi.useRealTimers()
  })

  it('activeRuns_and_atConcurrencyLimit', () => {
    const store = useSimulationStore()
    store.maxConcurrentRuns = 2
    store.runs = [
      runningRun({ runId: 'a', status: 'RUNNING' }),
      runningRun({ runId: 'b', status: 'PENDING' }),
      runningRun({ runId: 'c', status: 'COMPLETED' }),
    ]

    expect(store.activeRuns.map(r => r.runId)).toEqual(['a', 'b'])
    expect(store.hasActiveRun).toBe(true)
    expect(store.atConcurrencyLimit).toBe(true)
    expect(store.recentRuns.map(r => r.runId)).toEqual(['c'])
  })

  it('start_pushesOptimisticPlaceholderThenReconciles', async () => {
    const store = useSimulationStore()
    store.scenarios = [{ id: 's1', name: 'Scenario One', durationMinutes: 480 }]

    let resolveStart
    startRun.mockReturnValue(new Promise((resolve) => {
      resolveStart = resolve
    }))

    const startPromise = store.start('s1', 60)
    await nextTick()

    expect(store.activeRuns).toHaveLength(1)
    expect(store.activeRuns[0].status).toBe('PENDING')
    expect(store.activeRuns[0]._optimistic).toBe(true)
    expect(store.starting).toBe(true)

    resolveStart(runningRun({ runId: 'real-1', scenarioId: 's1', scenarioName: 'Scenario One' }))
    await startPromise

    expect(store.runs).toHaveLength(1)
    expect(store.runs[0].runId).toBe('real-1')
    expect(store.runs[0]._optimistic).toBeUndefined()
    expect(store.starting).toBe(false)
    expect(loadEncounters).toHaveBeenCalled()
  })

  it('start_rollsBackPlaceholderOnFailure', async () => {
    const store = useSimulationStore()
    startRun.mockRejectedValue(new Error('Maximum concurrent simulation runs (2) reached.'))

    await expect(store.start('s1', 60)).rejects.toThrow(/concurrent/)
    expect(store.runs).toEqual([])
    expect(store.error).toMatch(/concurrent/)
    expect(store.starting).toBe(false)
  })

  it('stop_updatesRunFromApi', async () => {
    const store = useSimulationStore()
    store.runs = [runningRun()]
    stopRun.mockResolvedValue(runningRun({ status: 'CANCELLED' }))

    await store.stop('run-1')
    expect(stopRun).toHaveBeenCalledWith('run-1')
    expect(store.runs[0].status).toBe('CANCELLED')
  })

  it('polling_runsOnlyWhileActiveRunExists', async () => {
    const store = useSimulationStore()
    store.startPolling()

    await vi.advanceTimersByTimeAsync(5_000)
    expect(fetchRuns).not.toHaveBeenCalled()

    store.runs = [runningRun()]
    await nextTick()

    await vi.advanceTimersByTimeAsync(2_000)
    expect(fetchRuns).toHaveBeenCalledTimes(1)

    fetchRuns.mockResolvedValue([runningRun({ status: 'COMPLETED', progressPercent: 100 })])
    await store.refreshRuns()
    await nextTick()

    fetchRuns.mockClear()
    await vi.advanceTimersByTimeAsync(4_000)
    expect(fetchRuns).not.toHaveBeenCalled()

    store.stopPolling()
  })

  it('loadConfig_failsClosedOnError', async () => {
    const store = useSimulationStore()
    fetchSimulationConfig.mockRejectedValue(new Error('network'))
    await store.loadConfig()
    expect(store.enabled).toBe(false)
    expect(store.maxConcurrentRuns).toBe(0)
  })
})
