/**
 * Tests for the React-idiomatic toast hook + provider.
 * @countermeasure-platform/web-components/services/toast-react
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import * as React from 'react'
import { render, screen, act } from '@testing-library/react'

import { ToastProvider, useToast } from '../toast-react'

/** Test harness: renders a child that fires `toast()` on demand. */
function Harness({ onReady }: { onReady: (api: ReturnType<typeof useToast>) => void }) {
  const api = useToast()
  React.useEffect(() => {
    onReady(api)
  }, [api, onReady])
  return null
}

describe('ToastProvider + useToast', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

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

  it('throws when useToast is called outside <ToastProvider>', () => {
    // Render swallows React's error log; we suppress it for this assertion.
    const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
    function Bad() {
      useToast()
      return null
    }
    expect(() => render(<Bad />)).toThrow(/useToast must be used within a <ToastProvider>/)
    spy.mockRestore()
  })

  it('renders the toast title in the viewport when toast() is called', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )
    expect(api).not.toBeNull()

    act(() => {
      api!.toast({ title: 'Hi' })
    })

    expect(screen.getByText('Hi')).toBeTruthy()
    expect(api!.toasts).toHaveLength(1)
    expect(api!.toasts[0]?.title).toBe('Hi')
  })

  it('returns an id, dismiss(), and update() on each toast() call', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )

    let handle: { id: string; dismiss: () => void; update: (o: any) => void } | null = null
    act(() => {
      handle = api!.toast({ title: 'First' })
    })
    expect(handle).not.toBeNull()
    expect(typeof handle!.id).toBe('string')
    expect(api!.toasts).toHaveLength(1)

    act(() => {
      handle!.update({ title: 'Updated' })
    })
    expect(screen.getByText('Updated')).toBeTruthy()

    act(() => {
      handle!.dismiss()
    })
    expect(api!.toasts).toHaveLength(0)
  })

  it('dismiss() with no id clears all toasts', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )

    act(() => {
      api!.toast({ title: 'A' })
      api!.toast({ title: 'B' })
      api!.toast({ title: 'C' })
    })
    expect(api!.toasts).toHaveLength(3)

    act(() => {
      api!.dismiss()
    })
    expect(api!.toasts).toHaveLength(0)
  })

  it('maps variant="destructive" to the error level', () => {
    let api: ReturnType<typeof useToast> | null = null
    const { container } = render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )

    act(() => {
      api!.toast({ title: 'Boom', variant: 'destructive' })
    })

    const toastEl = container.querySelector('[data-slot="toast"]')
    expect(toastEl).toBeTruthy()
    // The Toast primitive sets role="alert" for error/warning toasts.
    expect(toastEl?.getAttribute('role')).toBe('alert')
  })

  it('viewport renders bottom-right by default with the swipe direction set', () => {
    const { container } = render(
      <ToastProvider swipeDirection="right">
        <div />
      </ToastProvider>
    )
    const viewport = container.querySelector('[data-slot="toast-viewport"]')
    expect(viewport).toBeTruthy()
    expect(viewport?.getAttribute('data-swipe-direction')).toBe('right')
    const cls = viewport?.className ?? ''
    expect(cls).toContain('bottom-4')
    expect(cls).toContain('right-4')
  })

  it('honors the provider duration when none is passed on toast()', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider duration={1000}>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )

    act(() => {
      api!.toast({ title: 'Disappears' })
    })
    expect(api!.toasts).toHaveLength(1)

    act(() => {
      vi.advanceTimersByTime(1500)
    })
    expect(api!.toasts).toHaveLength(0)
  })

  it.each([
    ['success', 'status'],
    ['info', 'status'],
    ['warning', 'alert'],
    ['default', 'status'],
  ])('maps variant=%s onto a Toast with role=%s', (variant, role) => {
    let api: ReturnType<typeof useToast> | null = null
    const { container } = render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )
    act(() => {
      api!.toast({ title: 'lvl', variant: variant as 'success' | 'info' | 'warning' | 'default' })
    })
    const el = container.querySelector('[data-slot="toast"]')
    expect(el).toBeTruthy()
    expect(el?.getAttribute('role')).toBe(role)
  })

  it('passes description and action through to the underlying primitive', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )
    act(() => {
      api!.toast({
        title: <span>Title node</span>,
        description: ['arr ', 1, ' done'],
        action: <button>Undo</button>,
        duration: 200,
      })
    })
    expect(screen.getByText('Title node')).toBeTruthy()
    expect(screen.getByText(/arr 1 done/)).toBeTruthy()
    expect(screen.getByRole('button', { name: 'Undo' })).toBeTruthy()
  })

  it('forwards viewportClassName onto the viewport container', () => {
    const { container } = render(
      <ToastProvider viewportClassName="my-vp-cls">
        <div />
      </ToastProvider>
    )
    const vp = container.querySelector('[data-slot="toast-viewport"]')
    expect(vp?.className).toContain('my-vp-cls')
  })

  it.each(['left', 'up', 'down'] as const)('renders swipeDirection=%s on the viewport', dir => {
    const { container } = render(
      <ToastProvider swipeDirection={dir}>
        <div />
      </ToastProvider>
    )
    const vp = container.querySelector('[data-slot="toast-viewport"]')
    expect(vp?.getAttribute('data-swipe-direction')).toBe(dir)
  })

  it('dismisses one toast by id and leaves others', () => {
    let api: ReturnType<typeof useToast> | null = null
    render(
      <ToastProvider>
        <Harness
          onReady={a => {
            api = a
          }}
        />
      </ToastProvider>
    )
    let first: { id: string; dismiss: () => void; update: (o: any) => void } | null = null
    act(() => {
      first = api!.toast({ title: 'A' })
      api!.toast({ title: 'B' })
    })
    expect(api!.toasts).toHaveLength(2)
    act(() => {
      api!.dismiss(first!.id)
    })
    expect(api!.toasts).toHaveLength(1)
    expect(api!.toasts[0]?.title).toBe('B')
  })
})
