/** @vitest-environment jsdom */
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { BrowserApprovalToggle } from './BrowserApprovalToggle'

const postCollectorAction = vi.hoisted(() => vi.fn())
const useCollectorState = vi.hoisted(() => vi.fn())

vi.mock('../../lib/action-client', () => ({ postCollectorAction }))
vi.mock('../../lib/collector-queries', () => ({ useCollectorState }))

function stateWithGate(armedUntil: number | null) {
  return {
    data: {
      panels: [{
        id: 'permission-gate',
        ts: new Date().toISOString(),
        data: { armedUntil, armWindowMs: 60_000, waitMs: 30_000, pending: 0, maxPending: 64 },
      }],
      adapters: [],
    },
    refetch: vi.fn().mockResolvedValue(undefined),
  }
}

beforeEach(() => {
  postCollectorAction.mockReset().mockResolvedValue({ ok: true })
  useCollectorState.mockReset()
})

describe('BrowserApprovalToggle', () => {
  it('turns approvals on and reports the wait the terminal falls back after', async () => {
    useCollectorState.mockReturnValue(stateWithGate(null))
    render(<BrowserApprovalToggle />)

    const button = screen.getByRole('button', { name: 'Browser approvals off' })
    expect(button).toHaveAttribute('aria-pressed', 'false')

    fireEvent.click(button)
    await waitFor(() => expect(postCollectorAction).toHaveBeenCalledWith('permission.arm', {
      args: { state: 'on' },
      requestedBy: 'deck-web',
    }))
  })

  it('shows the armed state while the collector window is open and turns it off again', async () => {
    useCollectorState.mockReturnValue(stateWithGate(Date.now() + 60_000))
    render(<BrowserApprovalToggle />)

    const button = screen.getByRole('button', { name: 'Browser approvals on' })
    expect(button).toHaveAttribute('aria-pressed', 'true')
    expect(screen.getByText(/up to 30s/)).toBeInTheDocument()

    fireEvent.click(button)
    await waitFor(() => expect(postCollectorAction).toHaveBeenCalledWith('permission.arm', {
      args: { state: 'off' },
      requestedBy: 'deck-web',
    }))
  })

  it('treats a lapsed window as off rather than reporting a stale on', () => {
    useCollectorState.mockReturnValue(stateWithGate(Date.now() - 1_000))
    render(<BrowserApprovalToggle />)
    expect(screen.getByRole('button', { name: 'Browser approvals off' })).toBeInTheDocument()
  })

  it('says so instead of guessing when the collector reports no permission gate', () => {
    useCollectorState.mockReturnValue({ data: { panels: [], adapters: [] }, refetch: vi.fn() })
    render(<BrowserApprovalToggle />)
    expect(screen.getByText(/no permission gate/)).toBeInTheDocument()
    expect(screen.queryByRole('button')).toBeNull()
  })

  it('lets a hidden page lapse instead of refreshing the window it cannot keep alive', () => {
    vi.useFakeTimers()
    const visibility = vi.spyOn(document, 'visibilityState', 'get').mockReturnValue('hidden')
    try {
      useCollectorState.mockReturnValue(stateWithGate(Date.now() + 600_000))
      render(<BrowserApprovalToggle />)
      act(() => vi.advanceTimersByTime(90_000))
      expect(postCollectorAction).not.toHaveBeenCalled()

      visibility.mockReturnValue('visible')
      act(() => vi.advanceTimersByTime(20_000))
      expect(postCollectorAction).toHaveBeenCalledWith('permission.arm', {
        args: { state: 'on' },
        requestedBy: 'deck-web',
      })
    } finally {
      visibility.mockRestore()
      vi.useRealTimers()
    }
  })

  it('surfaces a failed toggle instead of showing a state it did not reach', async () => {
    useCollectorState.mockReturnValue(stateWithGate(null))
    postCollectorAction.mockRejectedValue(new Error('collector unreachable'))
    render(<BrowserApprovalToggle />)

    fireEvent.click(screen.getByRole('button', { name: 'Browser approvals off' }))
    expect(await screen.findByText('Could not turn approvals on')).toBeInTheDocument()
  })
})
