import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { ScanHistoryModal } from '../ScanHistoryModal'
import { TooltipProvider } from '@/components/ui/tooltip'

vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), warning: vi.fn() } }))
vi.mock('@/stores/canvasStore', () => ({
  useCanvasStore: { getState: () => ({ notifyScanDeviceFound: vi.fn() }) },
}))
vi.mock('@/api/client', () => ({
  scanApi: {
    runs: vi.fn().mockResolvedValue({ data: [] }),
    stop: vi.fn(),
  },
}))

import { scanApi } from '@/api/client'
import { toast } from 'sonner'

const RUNNING_RUN = {
  id: 'run-1',
  status: 'running',
  kind: 'ip',
  ranges: ['192.168.1.0/24'],
  devices_found: 2,
  started_at: new Date(Date.now() - 5000).toISOString(),
  finished_at: null,
  error: null,
}

const DONE_RUN = {
  id: 'run-2',
  status: 'done',
  kind: 'ip',
  ranges: ['192.168.1.0/24'],
  devices_found: 3,
  started_at: new Date(Date.now() - 60000).toISOString(),
  finished_at: new Date(Date.now() - 30000).toISOString(),
  error: null,
}

const CANCELLED_RUN = {
  id: 'run-3',
  status: 'cancelled',
  kind: 'ip',
  ranges: ['192.168.1.0/24'],
  devices_found: 1,
  started_at: new Date().toISOString(),
  finished_at: new Date().toISOString(),
  error: null,
}

const ZIGBEE_RUN = {
  id: 'run-4',
  status: 'done',
  kind: 'zigbee',
  ranges: [],
  devices_found: 7,
  started_at: new Date().toISOString(),
  finished_at: new Date().toISOString(),
  error: null,
}

const ZWAVE_RUN = {
  id: 'run-5',
  status: 'done',
  kind: 'zwave',
  ranges: [],
  devices_found: 5,
  started_at: new Date().toISOString(),
  finished_at: new Date().toISOString(),
  error: null,
}

const PROXMOX_RUN = {
  id: 'run-6',
  status: 'done',
  kind: 'proxmox',
  ranges: ['pve:8006'],
  devices_found: 9,
  started_at: new Date().toISOString(),
  finished_at: new Date().toISOString(),
  error: null,
}

function renderModal() {
  return render(
    <TooltipProvider>
      <ScanHistoryModal open onClose={vi.fn()} />
    </TooltipProvider>
  )
}

describe('ScanHistoryModal', () => {
  beforeEach(() => {
    vi.mocked(toast.success).mockReset()
    vi.mocked(toast.error).mockReset()
    vi.mocked(toast.warning).mockReset()
    vi.mocked(scanApi.stop).mockReset()
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [] } as never)
  })

  it('loads runs when opened', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN] } as never)
    renderModal()
    await waitFor(() => expect(scanApi.runs).toHaveBeenCalled())
    expect(await screen.findByText('done')).toBeDefined()
  })

  it('shows empty state when no scans', async () => {
    renderModal()
    expect(await screen.findByText('No scans yet')).toBeDefined()
  })

  it('shows stop button only for running scans', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [RUNNING_RUN, DONE_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getByText('running')).toBeDefined())
    expect(screen.getAllByRole('button', { name: 'Stop scan' })).toHaveLength(1)
  })

  it('calls scanApi.stop with the correct run ID', async () => {
    vi.mocked(scanApi.stop).mockResolvedValue({ data: { stopping: true } } as never)
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [RUNNING_RUN] } as never)
    renderModal()
    const stopBtn = await screen.findByRole('button', { name: 'Stop scan' })
    fireEvent.click(stopBtn)
    await waitFor(() => expect(scanApi.stop).toHaveBeenCalledWith('run-1'))
  })

  it('shows success toast when stop succeeds', async () => {
    vi.mocked(scanApi.stop).mockResolvedValue({ data: { stopping: true } } as never)
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [RUNNING_RUN] } as never)
    renderModal()
    const stopBtn = await screen.findByRole('button', { name: 'Stop scan' })
    fireEvent.click(stopBtn)
    await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Scan stop requested'))
  })

  it('shows error toast when stop fails', async () => {
    vi.mocked(scanApi.stop).mockRejectedValue(new Error('network'))
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [RUNNING_RUN] } as never)
    renderModal()
    const stopBtn = await screen.findByRole('button', { name: 'Stop scan' })
    fireEvent.click(stopBtn)
    await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Failed to stop scan'))
  })

  it('renders cancelled status without a stop button', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [CANCELLED_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getByText('cancelled')).toBeDefined())
    expect(screen.queryByRole('button', { name: 'Stop scan' })).toBeNull()
  })

  it('shows duration for a finished run', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN] } as never)
    renderModal()
    // DONE_RUN ran 30s
    expect(await screen.findByText('30s')).toBeDefined()
  })

  it('filters by status', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [RUNNING_RUN, DONE_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getByText('done')).toBeDefined())
    fireEvent.click(screen.getByRole('button', { name: 'Running' }))
    expect(screen.queryByText('done')).toBeNull()
    expect(screen.getByText('running')).toBeDefined()
  })

  it('filters by kind', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN, ZIGBEE_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getAllByText('done').length).toBe(2))
    fireEvent.click(screen.getByRole('button', { name: 'Zigbee' }))
    // Only the zigbee run (7 found) remains
    expect(screen.getByText('7 found')).toBeDefined()
    expect(screen.queryByText('3 found')).toBeNull()
  })

  it('filters by zwave kind', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN, ZWAVE_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getAllByText('done').length).toBe(2))
    fireEvent.click(screen.getByRole('button', { name: 'Z-Wave' }))
    // Only the zwave run (5 found) remains
    expect(screen.getByText('5 found')).toBeDefined()
    expect(screen.queryByText('3 found')).toBeNull()
  })

  it('renders a done proxmox run with an advisory as info, not a failure', async () => {
    const ADVISORY_RUN = {
      ...PROXMOX_RUN,
      id: 'run-7',
      devices_found: 3,
      error: 'Imported 3 host(s) but no VMs or LXC were visible to the API token. Grant PVEAuditor…',
    }
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [ADVISORY_RUN] } as never)
    renderModal()
    // Status stays "done" (success), yet the advisory text is surfaced.
    await waitFor(() => expect(screen.getByText('done')).toBeDefined())
    expect(screen.getByText(/no VMs or LXC were visible/)).toBeDefined()
  })

  it('shows a proxmox run under its own kind, not IP', async () => {
    vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN, PROXMOX_RUN] } as never)
    renderModal()
    await waitFor(() => expect(screen.getAllByText('done').length).toBe(2))
    // A dedicated Proxmox badge is rendered on the run (would be mislabeled "IP"
    // before the fix). Both the filter chip and the run badge carry the label.
    expect(screen.getAllByText('Proxmox').length).toBeGreaterThanOrEqual(2)
    // Filtering to Proxmox keeps only the proxmox run (9 found), drops the IP run.
    fireEvent.click(screen.getByRole('button', { name: 'Proxmox' }))
    expect(screen.getByText('9 found')).toBeDefined()
    expect(screen.queryByText('3 found')).toBeNull()
  })
})
