import { render, screen, fireEvent } from '@testing-library/react'
import { SessionCard, formatRelative } from '../session/SessionCard'
import { SessionList } from '../session/SessionList'
import { SessionStatusBadge } from '../session/SessionStatusBadge'
import { resolveSessionStatus, mapSocketStatus } from '../session/status'
import type { SessionSummary } from '../session/types'

const base: SessionSummary = {
  id: 's1',
  number: 7,
  name: 'Add billing page',
  isActive: true,
  projectName: 'raise-simpli',
  agentName: 'Claude',
  branch: 'feat/billing',
  createdAt: '2026-07-01T00:00:00.000Z',
}

// ---------------------------------------------------------------------------
// status helpers (pure)
// ---------------------------------------------------------------------------

describe('resolveSessionStatus', () => {
  it('prefers an explicit status kind + label', () => {
    const r = resolveSessionStatus({ status: 'error', statusLabel: 'Crashed', isActive: false })
    expect(r.kind).toBe('error')
    expect(r.label).toBe('Crashed')
    expect(r.className).toContain('red')
  })

  it('derives active/idle from isActive when status is absent', () => {
    expect(resolveSessionStatus({ isActive: true }).kind).toBe('active')
    expect(resolveSessionStatus({ isActive: false }).kind).toBe('idle')
  })

  it('supplies a default label per kind', () => {
    expect(resolveSessionStatus({ status: 'success', isActive: false }).label).toBe('Success')
  })
})

describe('mapSocketStatus', () => {
  it('maps agent socket status onto the terminal status', () => {
    expect(mapSocketStatus('open')).toBe('connected')
    expect(mapSocketStatus('connecting')).toBe('connecting')
    expect(mapSocketStatus('reconnecting')).toBe('connecting')
    expect(mapSocketStatus('idle')).toBe('disconnected')
    expect(mapSocketStatus('closed')).toBe('disconnected')
  })
})

describe('formatRelative', () => {
  it('formats a valid timestamp and passes an unparseable one through', () => {
    expect(formatRelative('2026-07-01T00:00:00.000Z')).toMatch(/ago|in /)
    expect(formatRelative('not-a-date')).toBe('not-a-date')
  })
})

// ---------------------------------------------------------------------------
// SessionStatusBadge
// ---------------------------------------------------------------------------

describe('SessionStatusBadge', () => {
  it('renders the resolved status label and tags the kind', () => {
    render(<SessionStatusBadge status="warning" />)
    const badge = screen.getByRole('status')
    expect(badge).toHaveAttribute('data-status', 'warning')
    expect(badge).toHaveTextContent('Needs review')
  })

  it('falls back to isActive when no status is given', () => {
    render(<SessionStatusBadge isActive />)
    expect(screen.getByRole('status')).toHaveAttribute('data-status', 'active')
  })
})

// ---------------------------------------------------------------------------
// SessionCard
// ---------------------------------------------------------------------------

describe('SessionCard', () => {
  it('renders name, branch, project, agent, number, and status', () => {
    render(<SessionCard session={base} />)
    expect(screen.getByText('Add billing page')).toBeInTheDocument()
    expect(screen.getByText('feat/billing')).toBeInTheDocument()
    expect(screen.getByText('raise-simpli')).toBeInTheDocument()
    expect(screen.getByText('Claude')).toBeInTheDocument()
    expect(screen.getByText('007')).toBeInTheDocument() // zero-padded number
    expect(screen.getByRole('status')).toHaveTextContent('Active')
  })

  it('renders the title as a link when href is provided', () => {
    render(<SessionCard session={base} href={(id) => `/sessions/${id}`} />)
    const link = screen.getByRole('link', { name: 'Add billing page' })
    expect(link).toHaveAttribute('href', '/sessions/s1')
  })

  it('calls onOpen when clicked (no href)', () => {
    const onOpen = jest.fn()
    render(<SessionCard session={base} onOpen={onOpen} />)
    fireEvent.click(screen.getByTestId('session-card'))
    expect(onOpen).toHaveBeenCalledWith('s1')
  })

  it('calls onOpenTerminal from the Terminal action without bubbling to the card', () => {
    const onOpen = jest.fn()
    const onOpenTerminal = jest.fn()
    render(<SessionCard session={base} onOpen={onOpen} onOpenTerminal={onOpenTerminal} />)
    fireEvent.click(screen.getByRole('button', { name: /terminal/i }))
    expect(onOpenTerminal).toHaveBeenCalledWith('s1')
    expect(onOpen).not.toHaveBeenCalled()
  })

  it('shows an unread dot when hasUnread', () => {
    render(<SessionCard session={{ ...base, hasUnread: true }} />)
    expect(screen.getByTestId('session-card-unread')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// SessionList
// ---------------------------------------------------------------------------

const sessions: SessionSummary[] = [
  base,
  { id: 's2', name: 'Fix auth bug', isActive: false, status: 'success', projectName: 'auth-web', branch: 'fix/auth', createdAt: '2026-07-02T00:00:00.000Z' },
  { id: 's3', name: 'Draft README', isActive: false, status: 'idle', projectName: 'raise-simpli', createdAt: '2026-07-03T00:00:00.000Z' },
]

describe('SessionList', () => {
  it('renders a card grid by default', () => {
    render(<SessionList sessions={sessions} />)
    expect(screen.getAllByTestId('session-card')).toHaveLength(3)
    expect(screen.getByText('Fix auth bug')).toBeInTheDocument()
  })

  it('renders a flat table layout', () => {
    render(<SessionList sessions={sessions} layout="table" />)
    expect(screen.getAllByTestId('session-row')).toHaveLength(3)
  })

  it('groups rows by project when groupByProject in table layout', () => {
    render(<SessionList sessions={sessions} layout="table" groupByProject />)
    // two project groups: raise-simpli (2) + auth-web (1)
    expect(screen.getByText('raise-simpli')).toBeInTheDocument()
    expect(screen.getByText('auth-web')).toBeInTheDocument()
    expect(screen.getByText('2 sessions')).toBeInTheDocument()
    expect(screen.getByText('1 session')).toBeInTheDocument()
  })

  it('collapses / expands a project group on header click', () => {
    render(<SessionList sessions={sessions} layout="table" groupByProject />)
    expect(screen.getAllByTestId('session-row')).toHaveLength(3)
    // collapse the raise-simpli group (its header carries the "2 sessions" count)
    fireEvent.click(screen.getByText('raise-simpli'))
    expect(screen.getAllByTestId('session-row')).toHaveLength(1)
  })

  it('fires onSessionClick from a row', () => {
    const onSessionClick = jest.fn()
    render(<SessionList sessions={sessions} layout="table" onSessionClick={onSessionClick} />)
    fireEvent.click(screen.getByText('Fix auth bug'))
    expect(onSessionClick).toHaveBeenCalledWith(expect.objectContaining({ id: 's2' }))
  })

  it('renders loading skeletons', () => {
    const { container } = render(<SessionList sessions={[]} isLoading skeletonCount={4} />)
    expect(container.querySelectorAll('.animate-pulse').length).toBeGreaterThan(0)
  })

  it('renders an error state', () => {
    render(<SessionList sessions={[]} error="nope" />)
    expect(screen.getByTestId('session-list-error')).toHaveTextContent('nope')
  })

  it('renders an empty state with a custom label', () => {
    render(<SessionList sessions={[]} emptyLabel="No runs yet" />)
    expect(screen.getByTestId('session-list-empty')).toHaveTextContent('No runs yet')
  })

  it('renders injected filters above the list', () => {
    render(<SessionList sessions={sessions} renderFilters={<div>my-filters</div>} />)
    expect(screen.getByText('my-filters')).toBeInTheDocument()
  })
})
