import { fireEvent, render, screen } from '@testing-library/react'
import type { ComponentProps } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { PlanTable } from './PlanTable'
import { PlanTableRow, type PlanTableItem } from './PlanTableRow'
import { planTableItemsFixture } from './fixtures/plans'

function renderRow(props: ComponentProps<typeof PlanTableRow>) {
  return render(<PlanTable items={[props.item]} selectedRunId={props.selected ? props.item.runId : null} onOpen={props.onOpen} onAbandon={props.onAbandon ?? vi.fn()} onResolve={props.onResolve ?? vi.fn()} />)
}

function rowProps(item: PlanTableItem): ComponentProps<typeof PlanTableRow> {
  return {
    item,
    selected: false,
    onOpen: vi.fn(),
    onAbandon: vi.fn(),
    onResolve: vi.fn(),
  }
}

describe('PlanTableRow', () => {
  beforeEach(() => {
    vi.useFakeTimers()
    vi.setSystemTime(Date.UTC(2026, 6, 19, 12))
  })
  afterEach(() => vi.useRealTimers())

  it('opens from row click while Open remains navigation', () => {
    const onOpen = vi.fn()
    renderRow({
      item: planTableItemsFixture[0]!,
      selected: true,
      onOpen,
      onAbandon: vi.fn(),
      onResolve: vi.fn(),
    })

    fireEvent.click(screen.getByTestId('plan-table-row'))
    expect(onOpen).toHaveBeenCalledWith(planTableItemsFixture[0])
    const openLink = screen.getByRole('link', { name: 'Open Gate hardening' })
    expect(openLink).toHaveAttribute('href', '/plans/run-failed')
    expect(openLink).toHaveAttribute('data-plan-run-link', 'run-failed')
    expect(screen.getByTestId('plan-table-row')).toHaveAttribute('data-selected', 'true')
    expect(screen.getByText('2026-07-18')).toHaveAttribute('data-tipb', '2026-07-18')
    expect(screen.getByText('5m ago')).toHaveAttribute('data-tipb', '2026-07-19T11:55:00.000Z')
    expect(screen.getByText('0 agents')).toBeInTheDocument()
    expect(screen.getByTestId('status-chip')).toHaveAttribute('data-status-category', 'failed')
  })

  it('shows running duration only for running rows', () => {
    renderRow({
      item: {
        ...planTableItemsFixture[1]!,
        status: 'running',
        runningStartedAtMs: null,
      },
      selected: false,
      onOpen: vi.fn(),
      onAbandon: vi.fn(),
      onResolve: vi.fn(),
    })

    const coverageGap = screen.getAllByText('—').find((element) => element.hasAttribute('data-tipb'))
    expect(coverageGap).toHaveAttribute(
      'data-tipb',
      'Missing harness /runs field: currentActivityStartedAt',
    )
  })

  it('shows a collapsed-attempts badge only when a plan has relaunches', () => {
    renderRow(rowProps({ ...planTableItemsFixture[0]!, attempts: 11 }))
    const badge = screen.getByTestId('plan-attempts')
    expect(badge).toHaveTextContent('×11')
    expect(badge).toHaveAttribute('data-tipb', '11 launches of this plan collapsed into this row — newest shown')
  })

  it('renders no attempts badge for a single-launch plan', () => {
    renderRow(rowProps(planTableItemsFixture[0]!))
    expect(screen.queryByTestId('plan-attempts')).toBeNull()
  })

  it('renders a stalled indicator when the row is alarmed', () => {
    renderRow(rowProps({ ...planTableItemsFixture[0]!, alarmed: true }))
    expect(screen.getByTestId('plan-run-alarmed')).toHaveTextContent('stalled')
  })

  it('omits the stalled indicator when the row is not alarmed', () => {
    renderRow(rowProps(planTableItemsFixture[0]!))
    expect(screen.queryByTestId('plan-run-alarmed')).toBeNull()
  })

  it('renders the elapsed activity duration when a running row carries a start time', () => {
    renderRow(rowProps({
      ...planTableItemsFixture[1]!,
      status: 'running',
      runningStartedAtMs: Date.UTC(2026, 6, 19, 11, 30),
    }))

    expect(screen.getByText('30m')).toHaveAttribute('data-tipb', '2026-07-19T11:30:00.000Z')
  })

  it('omits the duplicate Open run action and fires abandon from the trash control', () => {
    const onAbandon = vi.fn()
    const onResolve = vi.fn()
    const item = { ...planTableItemsFixture[0]!, failClass: undefined }
    renderRow({ item, selected: false, onOpen: vi.fn(), onAbandon, onResolve })

    expect(screen.queryByRole('button', { name: 'Open run' })).not.toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Abandon (soft delete)' }))
    expect(onAbandon).toHaveBeenCalledWith(item)
    expect(onResolve).not.toHaveBeenCalled()
  })

  it('uses the shared failed category for row actions', () => {
    const degraded = { ...planTableItemsFixture[0]!, status: 'degraded', failClass: undefined }
    renderRow(rowProps(degraded))

    expect(screen.queryByRole('button', { name: 'Open run' })).not.toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'Abandon (soft delete)' })).toBeInTheDocument()
  })

  it('does not expose failed actions when needs-you is the rendered category', () => {
    renderRow(rowProps({ ...planTableItemsFixture[0]!, needsYou: true }))

    expect(screen.queryByRole('button', { name: 'Open run' })).not.toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'Abandon (soft delete)' })).not.toBeInTheDocument()
  })

  it('disables unresolved harness actions with the pinned gap tooltip', () => {
    const item = { ...planTableItemsFixture[0]!, failClass: 'wrapper-engine-down' }
    renderRow({
      item,
      selected: false,
      onOpen: vi.fn(),
      onAbandon: vi.fn(),
      onResolve: vi.fn(),
    })

    expect(screen.getByRole('button', { name: 'Retry' })).toBeDisabled()
    expect(screen.getByText('Retry').parentElement).toHaveAttribute('data-tipb', 'needs harness B3/B7')
  })

  it('folds more than two actions into a menu with abandon last', () => {
    const onAbandon = vi.fn()
    const item: PlanTableItem = {
      ...planTableItemsFixture[0]!,
      additionalResolveActions: [{ label: 'Open review findings', kind: 'review' }],
    }
    renderRow({
      item,
      selected: false,
      onOpen: vi.fn(),
      onAbandon,
      onResolve: vi.fn(),
    })

    fireEvent.click(screen.getByRole('button', { name: 'Actions' }))
    const items = screen.getAllByRole('menuitem')
    expect(items.map((entry) => entry.textContent)).toEqual([
      'Inspect gate log',
      'Open review findings',
      'Abandon (soft delete)',
    ])
const separator = screen.getByRole('separator')
    expect(
      separator.compareDocumentPosition(items[0]!) & Node.DOCUMENT_POSITION_PRECEDING,
    ).toBeTruthy()
    expect(
      separator.compareDocumentPosition(items[2]!) & Node.DOCUMENT_POSITION_FOLLOWING,
    ).toBeTruthy()
    fireEvent.click(items[2]!)
    expect(onAbandon).toHaveBeenCalledWith(item)
  })
})
