import { act, renderHook, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { CampaignListItem } from '../api'
import {
  type CampaignReadAdapter,
  type CampaignReportSnapshot,
  useCampaignReport,
} from './useCampaignReport'

const campaign = { id: 'campaign-1', status: 'running' } as CampaignListItem
const terminalCampaign = { ...campaign, status: 'completed' } as CampaignListItem
const snapshot = {
  report: { campaign_id: 'campaign-1', status: 'running' },
  runs: [{ id: 'run-1', campaign_id: 'campaign-1' }],
  timeline: [{ run_id: 'run-1' }],
  analysis: { status: 'none' },
  comparison: { status: 'none' },
} as CampaignReportSnapshot

function deferred<T>() {
  let resolve!: (value: T) => void
  const promise = new Promise<T>((resolvePromise) => { resolve = resolvePromise })
  return { promise, resolve }
}

function createAdapter(overrides: Partial<CampaignReadAdapter> = {}): CampaignReadAdapter {
  return {
    list: vi.fn().mockResolvedValue([]),
    report: vi.fn().mockResolvedValue(snapshot),
    timeline: vi.fn().mockResolvedValue([]),
    generateAnalysis: vi.fn().mockResolvedValue(undefined),
    generateComparison: vi.fn().mockResolvedValue(undefined),
    ...overrides,
  }
}

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

describe('useCampaignReport', () => {
  it('loads complete list and report snapshots', async () => {
    const adapter = createAdapter({
      list: vi.fn().mockResolvedValue([campaign]),
      report: vi.fn().mockResolvedValue(snapshot),
    })
    const { result } = renderHook(() => useCampaignReport('campaign-1', true, adapter))

    await waitFor(() => expect(result.current.report.value).toBe(snapshot))

    expect(result.current.list.value).toEqual([campaign])
    expect(result.current.list.phase).toBe('ready')
    expect(result.current.report.phase).toBe('ready')
  })

  it('retains the last complete snapshot when a silent refresh fails', async () => {
    const adapter = createAdapter({
      list: vi.fn().mockResolvedValue([campaign]),
      report: vi.fn()
        .mockResolvedValueOnce(snapshot)
        .mockRejectedValueOnce(new Error('network error')),
    })
    const { result } = renderHook(() => useCampaignReport('campaign-1', true, adapter))
    await waitFor(() => expect(result.current.report.value).toBe(snapshot))

    await act(async () => { await result.current.reloadReport() })

    expect(result.current.report.phase).toBe('ready')
    expect(result.current.report.value).toBe(snapshot)
    expect(result.current.report.error).toBeNull()
  })

  it('ignores a late report response for the previous campaign', async () => {
    const first = deferred<CampaignReportSnapshot>()
    const second = deferred<CampaignReportSnapshot>()
    const secondSnapshot = {
      ...snapshot,
      report: { ...snapshot.report, campaign_id: 'campaign-2' },
    } as CampaignReportSnapshot
    const adapter = createAdapter({
      list: vi.fn().mockResolvedValue([]),
      report: vi.fn((id: string) => (id === 'campaign-1' ? first.promise : second.promise)),
    })
    const { result, rerender } = renderHook(
      ({ selectedId }) => useCampaignReport(selectedId, true, adapter),
      { initialProps: { selectedId: 'campaign-1' as string | null } },
    )

    rerender({ selectedId: 'campaign-2' })
    expect(result.current.report.value).toBeNull()

    await act(async () => { second.resolve(secondSnapshot) })
    expect(result.current.report.value?.report.campaign_id).toBe('campaign-2')

    await act(async () => { first.resolve(snapshot) })
    expect(result.current.report.value?.report.campaign_id).toBe('campaign-2')
  })

  it('polls active lists and stops after a terminal snapshot', async () => {
    vi.useFakeTimers()
    const adapter = createAdapter({
      list: vi.fn()
        .mockResolvedValueOnce([campaign])
        .mockResolvedValue([terminalCampaign]),
      report: vi.fn().mockResolvedValue({
        ...snapshot,
        report: { ...snapshot.report, status: 'completed' },
      }),
    })
    renderHook(() => useCampaignReport(null, true, adapter))
    await act(async () => { await Promise.resolve() })

    await act(async () => { await vi.advanceTimersByTimeAsync(5000) })
    await act(async () => { await vi.advanceTimersByTimeAsync(10000) })

    expect(adapter.list).toHaveBeenCalledTimes(2)
  })

  it('refreshes the complete snapshot after analysis and comparison mutations', async () => {
    const refreshed = {
      ...snapshot,
      analysis: { status: 'queued' },
      comparison: { ...snapshot.comparison, status: 'queued' },
    } as CampaignReportSnapshot
    const adapter = createAdapter({
      list: vi.fn().mockResolvedValue([campaign]),
      report: vi.fn().mockResolvedValueOnce(snapshot).mockResolvedValue(refreshed),
    })
    const { result } = renderHook(() => useCampaignReport('campaign-1', true, adapter))
    await waitFor(() => expect(result.current.report.value).toBe(snapshot))

    await act(async () => { await result.current.generateAnalysis() })
    expect(adapter.generateAnalysis).toHaveBeenCalledWith('campaign-1')
    expect(result.current.report.value).toBe(refreshed)

    await act(async () => { await result.current.generateComparison('campaign-0') })
    expect(adapter.generateComparison).toHaveBeenCalledWith('campaign-1', 'campaign-0')
    expect(adapter.report).toHaveBeenCalledTimes(3)
  })

  it('owns expanded timeline loading', async () => {
    const entries = [{ run_id: 'run-2' }] as CampaignReportSnapshot['timeline']
    const adapter = createAdapter({ timeline: vi.fn().mockResolvedValue(entries) })
    const { result } = renderHook(() => useCampaignReport(null, true, adapter))

    act(() => { result.current.setTimelineExpanded('campaign-1', true) })
    await waitFor(() => expect(result.current.timelines['campaign-1']).toBe(entries))
    expect(result.current.expandedIds).toEqual(['campaign-1'])

    act(() => { result.current.setTimelineExpanded('campaign-1', false) })
    expect(result.current.expandedIds).toEqual([])
  })
})
