import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, act, fireEvent } from '@testing-library/react'
import { ToastProvider, useToast } from './Toast'

// ── Helpers ───────────────────────────────────────────────────────────────

/** Renders a ToastProvider and exposes the useToast API via a ref-like object */
function renderProvider() {
  let api!: ReturnType<typeof useToast>

  function Consumer() {
    api = useToast()
    return null
  }

  render(
    <ToastProvider>
      <Consumer />
    </ToastProvider>,
  )

  return { getApi: () => api }
}

// ── Tests ─────────────────────────────────────────────────────────────────

describe('Toast', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

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

  it('shows a toast when toast() is called', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Hello', variant: 'info' }) })

    expect(screen.getByTestId('toast')).toBeInTheDocument()
    expect(screen.getByText('Hello')).toBeInTheDocument()
  })

  it('renders title and description', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Success!', variant: 'success', description: 'It worked.' }) })

    expect(screen.getByText('Success!')).toBeInTheDocument()
    expect(screen.getByText('It worked.')).toBeInTheDocument()
  })

  it('applies correct variant data attribute — error', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Error!', variant: 'error' }) })

    expect(screen.getByTestId('toast')).toHaveAttribute('data-variant', 'error')
  })

  it('applies success variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Success!', variant: 'success' }) })

    expect(screen.getByTestId('toast')).toHaveAttribute('data-variant', 'success')
  })

  it('applies warning variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Warning!', variant: 'warning' }) })

    expect(screen.getByTestId('toast')).toHaveAttribute('data-variant', 'warning')
  })

  it('applies info variant by default when no variant given', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Default' }) })

    expect(screen.getByTestId('toast')).toHaveAttribute('data-variant', 'info')
  })

  it('shows correct icon for info variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Info', variant: 'info' }) })

    expect(screen.getByTestId('toast').querySelector('[aria-hidden="true"] svg')).toBeInTheDocument()
  })

  it('shows correct icon for success variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'OK', variant: 'success' }) })

    expect(screen.getByTestId('toast').querySelector('[aria-hidden="true"] svg')).toBeInTheDocument()
  })

  it('shows correct icon for warning variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Warn', variant: 'warning' }) })

    expect(screen.getByTestId('toast').querySelector('[aria-hidden="true"] svg')).toBeInTheDocument()
  })

  it('shows correct icon for error variant', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Err', variant: 'error' }) })

    expect(screen.getByTestId('toast').querySelector('[aria-hidden="true"] svg')).toBeInTheDocument()
  })

  it('dismisses toast when close button is clicked', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Closeable' }) })
    expect(screen.getByTestId('toast')).toBeInTheDocument()

    fireEvent.click(screen.getByLabelText('Dismiss notification'))

    // After exit animation duration
    act(() => { vi.advanceTimersByTime(300) })

    expect(screen.queryByTestId('toast')).not.toBeInTheDocument()
  })

  it('auto-dismisses after default duration (5000ms)', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Auto' }) })
    expect(screen.getByTestId('toast')).toBeInTheDocument()

    // Advance past default duration + exit animation
    act(() => { vi.advanceTimersByTime(5000 + 300) })

    expect(screen.queryByTestId('toast')).not.toBeInTheDocument()
  })

  it('is still visible just before auto-dismiss fires', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Auto', duration: 1000 }) })

    act(() => { vi.advanceTimersByTime(999) })

    expect(screen.getByTestId('toast')).toBeInTheDocument()
  })

  it('auto-dismisses after custom duration', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Fast', duration: 1000 }) })

    act(() => { vi.advanceTimersByTime(1000 + 300) })

    expect(screen.queryByTestId('toast')).not.toBeInTheDocument()
  })

  it('renders multiple toasts', () => {
    const { getApi } = renderProvider()

    act(() => {
      getApi().toast({ title: 'First', variant: 'info' })
      getApi().toast({ title: 'Second', variant: 'error' })
      getApi().toast({ title: 'Third', variant: 'warning' })
    })

    expect(screen.getAllByTestId('toast')).toHaveLength(3)
  })

  it('caps visible toasts at 5', () => {
    const { getApi } = renderProvider()

    act(() => {
      for (let i = 0; i < 7; i++) {
        getApi().toast({ title: `Toast ${i}` })
      }
    })

    expect(screen.getAllByTestId('toast')).toHaveLength(5)
  })

  it('keeps newest 5 when capped', () => {
    const { getApi } = renderProvider()

    act(() => {
      for (let i = 0; i < 7; i++) {
        getApi().toast({ title: `Toast ${i}` })
      }
    })

    // Toast 0 and 1 should be gone (oldest), Toast 2-6 remain
    expect(screen.queryByText('Toast 0')).not.toBeInTheDocument()
    expect(screen.queryByText('Toast 1')).not.toBeInTheDocument()
    expect(screen.getByText('Toast 6')).toBeInTheDocument()
  })

  it('returns a string id from toast()', () => {
    const { getApi } = renderProvider()

    let id!: string
    act(() => { id = getApi().toast({ title: 'Test' }) })

    expect(typeof id).toBe('string')
    expect(id.length).toBeGreaterThan(0)
  })

  it('dismiss() by id removes only the specified toast', () => {
    const { getApi } = renderProvider()

    let id1!: string
    act(() => {
      id1 = getApi().toast({ title: 'First' })
      getApi().toast({ title: 'Second' })
    })

    expect(screen.getAllByTestId('toast')).toHaveLength(2)

    act(() => { getApi().dismiss(id1) })
    act(() => { vi.advanceTimersByTime(300) })

    expect(screen.getAllByTestId('toast')).toHaveLength(1)
    expect(screen.queryByText('First')).not.toBeInTheDocument()
    expect(screen.getByText('Second')).toBeInTheDocument()
  })

  it('toast container has aria-live attribute', () => {
    const { getApi } = renderProvider()

    act(() => { getApi().toast({ title: 'Accessible' }) })

    expect(screen.getByLabelText('Notifications')).toBeInTheDocument()
    expect(screen.getByLabelText('Notifications')).toHaveAttribute('aria-live', 'polite')
  })

  it('throws if useToast is used outside ToastProvider', () => {
    function BadComponent() {
      useToast()
      return null
    }

    const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
    expect(() => render(<BadComponent />)).toThrow('useToast must be used within a ToastProvider')
    consoleSpy.mockRestore()
  })
})
