import React from 'react'
import { describe, expect, it, vi } from 'vitest'

import { renderWithProviders, screen, fireEvent } from '../../test/utils'

import RichCard from '.'

const HERO = 'https://cdn.example.com/hero.jpg'
const IMG_2 = 'https://cdn.example.com/two.jpg'
const IMG_3 = 'https://cdn.example.com/three.jpg'

describe('RichCard', () => {
  it('renders the title and subtitle', async () => {
    renderWithProviders(
      <RichCard.Received
        title="Set up your welcome message"
        subtitle="Automatically greet new followers."
        images={[HERO]}
      />
    )
    expect(await screen.findByTestId('rich-card')).toBeInTheDocument()
    expect(screen.getByText('Set up your welcome message')).toBeInTheDocument()
    expect(
      screen.getByText('Automatically greet new followers.')
    ).toBeInTheDocument()
  })

  it('renders multiline bullet copy as separate lines', async () => {
    renderWithProviders(
      <RichCard.Received
        title="Welcome"
        subtitle={
          '👋 Say hello as people arrive\n✨ Spark conversations\n💬 Add automated FAQs'
        }
        subtitleLines={6}
        images={[HERO]}
      />
    )

    const card = await screen.findByTestId('rich-card')
    expect(card).toHaveTextContent('👋 Say hello as people arrive')
    expect(card).toHaveTextContent('✨ Spark conversations')
    expect(card).toHaveTextContent('💬 Add automated FAQs')
    expect(card.querySelectorAll('br')).toHaveLength(2)
  })

  it('does not insert line breaks when the subtitle is clamped to one line', async () => {
    renderWithProviders(
      <RichCard.Received
        title="Welcome"
        subtitle={
          '👋 Say hello as people arrive\n✨ Spark conversations\n💬 Add automated FAQs'
        }
        subtitleLines={1}
        images={[HERO]}
      />
    )

    const card = await screen.findByTestId('rich-card')
    expect(card.querySelectorAll('br')).toHaveLength(0)
    expect(screen.getByText(/Say hello as people arrive/)).toHaveClass(
      'truncate'
    )
  })

  it('renders a single hero image for the hero presentation', async () => {
    renderWithProviders(
      <RichCard.Received
        title="Welcome"
        images={[HERO]}
        imagePresentation="hero"
      />
    )
    await screen.findByTestId('rich-card')
    const images = screen.getAllByRole('img')
    expect(images).toHaveLength(1)
    expect(images[0]).toHaveAttribute('alt', 'Welcome')
  })

  it('renders up to three fanned images for the fan presentation', async () => {
    const { container } = renderWithProviders(
      <RichCard.Received
        title="Collection"
        images={[HERO, IMG_2, IMG_3]}
        imagePresentation="fan"
      />
    )
    await screen.findByTestId('rich-card')
    // Three <img> nodes render; all are decorative (alt=""), so the fan is out
    // of the a11y tree — the accessible content is the card's title/subtitle.
    expect(container.querySelectorAll('img')).toHaveLength(3)
    expect(screen.queryByAltText('Collection')).toBeNull()
  })

  it('drops a failed fan image and re-fans the survivors', async () => {
    const { container } = renderWithProviders(
      <RichCard.Received
        title="Collection"
        images={[HERO, IMG_2, IMG_3]}
        imagePresentation="fan"
      />
    )
    await screen.findByTestId('rich-card')
    const images = container.querySelectorAll('img')
    expect(images).toHaveLength(3)
    // A decorative tile 404s — it's removed and the survivors re-fan.
    fireEvent.error(images[2])
    expect(container.querySelectorAll('img')).toHaveLength(2)
  })

  it('keeps fan thumbnails decorative (empty alt)', async () => {
    const { container } = renderWithProviders(
      <RichCard.Received
        title="Collection"
        images={[HERO, IMG_2, IMG_3]}
        imagePresentation="fan"
      />
    )
    await screen.findByTestId('rich-card')
    const imgs = Array.from(container.querySelectorAll('img'))
    expect(imgs).toHaveLength(3)
    expect(imgs.every((img) => img.getAttribute('alt') === '')).toBe(true)
  })

  it('resets failed fan tiles when the image set changes', async () => {
    const { container, rerender } = renderWithProviders(
      <RichCard.Received
        title="Collection"
        images={[HERO, IMG_2, IMG_3]}
        imagePresentation="fan"
      />
    )
    await screen.findByTestId('rich-card')
    fireEvent.error(container.querySelectorAll('img')[2])
    expect(container.querySelectorAll('img')).toHaveLength(2)
    // A recycled card with a new collection clears the stale failure tracking.
    rerender(
      <RichCard.Received
        title="Collection"
        images={[
          'https://cdn.example.com/a.jpg',
          'https://cdn.example.com/b.jpg',
          'https://cdn.example.com/c.jpg',
        ]}
        imagePresentation="fan"
      />
    )
    expect(container.querySelectorAll('img')).toHaveLength(3)
  })

  it('defaults presentation from image count (>=2 fans)', async () => {
    const { container } = renderWithProviders(
      <RichCard.Sent title="Two" images={[HERO, IMG_2]} />
    )
    await screen.findByTestId('rich-card')
    expect(container.querySelectorAll('img')).toHaveLength(2)
  })

  it('drops a failed hero image to the placeholder rather than a broken image', async () => {
    const { container } = renderWithProviders(
      <RichCard.Received
        title="Welcome"
        images={[HERO]}
        imagePresentation="hero"
      />
    )
    const img = await screen.findByRole('img')
    // The hero errors → useChinPalette clears the URL → CardThumbnail falls
    // through to its type-icon placeholder, so no <img> remains.
    fireEvent.error(img)
    expect(container.querySelectorAll('img')).toHaveLength(0)
  })

  it('does not pin the card to a fixed height (grows for tall content)', async () => {
    renderWithProviders(
      <RichCard.Received
        title="Card"
        subtitle="A longer prose subtitle that wraps across a few lines."
        images={[HERO]}
        actions={[
          { label: 'One', variant: 'primary' },
          { label: 'Two', variant: 'primary' },
          { label: 'Three', variant: 'primary' },
        ]}
      />
    )
    const card = await screen.findByTestId('rich-card')
    expect(card.className).not.toContain('h-[250px]')
  })

  it('renders no image region when there are no images', async () => {
    renderWithProviders(
      <RichCard.Received title="Text only" subtitle="No hero here." />
    )
    await screen.findByTestId('rich-card')
    expect(screen.queryAllByRole('img')).toHaveLength(0)
    expect(screen.getByText('Text only')).toBeInTheDocument()
  })

  it('renders a url action as an external anchor, others as buttons', async () => {
    const onClick = vi.fn()
    renderWithProviders(
      <RichCard.Received
        title="Card"
        images={[HERO]}
        actions={[
          { label: 'Shop now', href: 'tr.ee/x', variant: 'secondary' },
          { label: 'Set up', onClick, variant: 'secondary' },
        ]}
      />
    )
    // Secondary labels render inside a truncating span, so resolve the anchor/
    // button from the label node.
    const link = (await screen.findByText('Shop now')).closest('a')
    expect(link).not.toBeNull()
    expect(link).toHaveAttribute('href', 'https://tr.ee/x')
    expect(link).toHaveAttribute('target', '_blank')

    const button = screen.getByText('Set up').closest('button')
    expect(button).not.toBeNull()
    fireEvent.click(button as HTMLButtonElement)
    expect(onClick).toHaveBeenCalledTimes(1)
  })

  it('stacks a primary footer full-width and lays a secondary footer inline', async () => {
    const { rerender } = renderWithProviders(
      <RichCard.Received
        title="Card"
        images={[HERO]}
        actions={[{ label: 'Set up welcome message', variant: 'primary' }]}
      />
    )
    // Primary → stacked, full-width button.
    expect(await screen.findByText('Set up welcome message')).toHaveClass(
      'w-full'
    )

    rerender(
      <RichCard.Received
        title="Card"
        images={[HERO]}
        actions={[{ label: 'Shop now', href: 'tr.ee/x', variant: 'secondary' }]}
      />
    )
    // Secondary → inline, content-width button (no w-full).
    expect(screen.getByText('Shop now').closest('a')).not.toHaveClass('w-full')
  })

  it('lays a secondary chin out as a row: copy left, action right', async () => {
    const { rerender } = renderWithProviders(
      <RichCard.Received
        title="Test collection"
        subtitle="Collection of 3 links"
        images={[HERO, IMG_2, IMG_3]}
        actions={[{ label: 'Open', href: 'tr.ee/c', variant: 'secondary' }]}
      />
    )
    // The action shares one justify-between row with the title/subtitle copy.
    const link = await screen.findByText('Open')
    const row = link.closest('.justify-between')
    expect(row).not.toBeNull()
    expect(row?.textContent).toContain('Test collection')

    // Primary stays stacked below the copy — no chin row.
    rerender(
      <RichCard.Received
        title="Welcome"
        images={[HERO]}
        actions={[{ label: 'Set up', variant: 'primary' }]}
      />
    )
    expect(screen.getByText('Set up').closest('.justify-between')).toBeNull()
  })
})
