/** @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import type { ActivityEvent, ActivitySourceEntriesResponse } from '../../lib/activity-types'
import { SourceEntriesBody, sourceEntriesInitialState } from './SourceEntriesContent'

function event(overrides: Partial<ActivityEvent> = {}): ActivityEvent {
  return {
    id: 'actions:1',
    ts: '2026-08-08T10:00:00.000Z',
    category: 'service',
    source: 'actions',
    actor: 'human',
    severity: 'info',
    title: 'reap — ok',
    ...overrides,
  }
}

function response(overrides: Partial<ActivitySourceEntriesResponse> = {}): ActivitySourceEntriesResponse {
  return {
    source: {
      id: 'actions',
      label: 'collector actions',
      category: 'service',
      path: '/home/user/.local/state/overdeck/actions.jsonl',
      storage: 'test source',
      queryBounds: 'test bounds',
      status: 'ok',
      records: 2,
      totalRecords: 2,
      skipped: 0,
    },
    events: [],
    total: 0,
    offset: 0,
    limit: 100,
    skipped: [],
    skippedTotal: 0,
    skippedTruncated: false,
    ...overrides,
  }
}

function renderBody(data: ActivitySourceEntriesResponse, handlers: Partial<Record<string, unknown>> = {}) {
  const onOffset = vi.fn()
  const onSelect = vi.fn()
  render(
    <SourceEntriesBody
      data={data}
      sourceId={data.source.id}
      range="7d"
      severityFloor={undefined}
      searchText=""
      offset={data.offset}
      selectedId={(handlers.selectedId as string | undefined) ?? null}
      onRange={vi.fn()}
      onSeverity={vi.fn()}
      onSearch={vi.fn()}
      onOffset={onOffset}
      onSelect={onSelect}
    />,
  )
  return { onOffset, onSelect }
}

describe('SourceEntriesBody', () => {
  it('opens URL-selected evidence across the full source history', () => {
    expect(sourceEntriesInitialState('?event=actions%3A42')).toEqual({ range: 'all', recordId: 'actions:42' })
    expect(sourceEntriesInitialState('')).toEqual({ range: '7d', recordId: null })
  })

  it('renders each individual entry of the source', () => {
    renderBody(response({
      events: [event(), event({ id: 'actions:2', title: 'steer — failed', severity: 'error' })],
      total: 2,
    }))

    expect(screen.getByText('reap — ok')).toBeInTheDocument()
    expect(screen.getByText('steer — failed')).toBeInTheDocument()
    expect(screen.getByText('Showing 1–2 of 2')).toBeInTheDocument()
  })

  it('sorts entries by their raw timestamp', () => {
    renderBody(response({
      events: [
        event({ id: 'actions:old', title: 'older entry', ts: '2026-08-08T09:00:00.000Z' }),
        event({ id: 'actions:new', title: 'newer entry', ts: '2026-08-08T11:00:00.000Z' }),
      ],
      total: 2,
    }))
    const table = screen.getByRole('table', { name: 'collector actions entries' })
    expect(within(table).getAllByRole('row')[1]).toHaveTextContent('newer entry')
    fireEvent.click(within(table).getByRole('button', { name: /Time/i }))
    expect(within(table).getAllByRole('row')[1]).toHaveTextContent('older entry')
  })

  it('opens the selected entry details in a drawer', () => {
    renderBody(response({ events: [event({ account: 'claude-work', detail: { operation: 'reap' } })], total: 1 }))
    const table = screen.getByRole('table', { name: 'collector actions entries' })
    expect(within(table).getByRole('columnheader', { name: /^Account/ })).toBeInTheDocument()
    expect(within(table).getByText('claude-work')).toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Details' }))
    const drawer = screen.getByRole('dialog')
    expect(drawer).toHaveTextContent('actions:1')
    expect(drawer).toHaveTextContent('account')
    expect(drawer).toHaveTextContent('claude-work')
    expect(drawer).toHaveTextContent('detail.operation')
    expect(drawer).toHaveTextContent('reap')
  })

  it('explains skipped records instead of only counting them', () => {
    renderBody(response({
      skippedTotal: 1,
      skipped: [{
        id: 'actions:skip:1',
        reason: 'unparseable-json',
        explanation: 'the record is not valid JSON',
        path: '/home/user/.local/state/overdeck/actions.jsonl',
        line: 42,
        excerpt: 'not json at all',
      }],
    }))

    const table = screen.getByRole('table', { name: 'collector actions skipped records' })
    expect(within(table).getByText('line 42')).toBeInTheDocument()
    expect(within(table).getByText('unparseable-json')).toBeInTheDocument()
    expect(within(table).getByText('the record is not valid JSON')).toBeInTheDocument()
    expect(within(table).getByText('not json at all')).toBeInTheDocument()
  })

  it('shows an em dash rather than a fabricated line number when the skip has no locator', () => {
    renderBody(response({
      skippedTotal: 1,
      skipped: [{
        id: 'agent-sessions:skip:1',
        reason: 'unsupported-schema',
        explanation: 'the record declares a schema this reader does not understand — schemaVersion=2, expected 1',
        path: '/home/user/.local/state/agent-sessions/sessions/a.json',
      }],
    }))

    const table = screen.getByRole('table', { name: 'collector actions skipped records' })
    expect(within(table).getAllByText('—')).toHaveLength(2)
    expect(within(table).queryByText(/^line /)).not.toBeInTheDocument()
  })

  it('pages forward and back over the source total', () => {
    const { onOffset } = renderBody(response({
      events: [event()],
      total: 250,
      offset: 100,
      limit: 100,
    }))

    expect(screen.getByText('Showing 101–101 of 250')).toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Older' }))
    expect(onOffset).toHaveBeenCalledWith(200)
    fireEvent.click(screen.getByRole('button', { name: 'Newer' }))
    expect(onOffset).toHaveBeenCalledWith(0)
  })

  it('reports an absent source as absent rather than as an empty log', () => {
    renderBody(response({
      source: {
        id: 'actions',
        label: 'collector actions',
        category: 'service',
        path: '/missing/actions.jsonl',
        storage: 'test source',
        queryBounds: 'test bounds',
        status: 'absent',
        records: 0,
        totalRecords: 0,
        skipped: 0,
      },
    }))

    expect(screen.getByText(/not recorded on this machine/)).toBeInTheDocument()
  })

  it('surfaces a read error with the collector message', () => {
    renderBody(response({
      source: {
        id: 'actions',
        label: 'collector actions',
        category: 'service',
        path: '/denied/actions.jsonl',
        storage: 'test source',
        queryBounds: 'test bounds',
        status: 'error',
        records: 0,
        totalRecords: 0,
        skipped: 0,
        error: 'EACCES: permission denied',
      },
    }))

    expect(screen.getAllByText('EACCES: permission denied').length).toBeGreaterThan(0)
  })

  it('links back to the sources list', () => {
    renderBody(response())
    expect(screen.getByRole('link', { name: 'Back to all sources' })).toHaveAttribute('href', '/logs')
  })
})
