/** @vitest-environment jsdom */
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import React from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fetchRequests, fetchRequestStory } from '../../lib/collector-client'
import { postRequestAnswer } from '../../lib/request-answer'
import { openSessionTerminal } from '../sessions/session-terminal'
import { RequestsContent } from './RequestsContent'

vi.mock('../../lib/collector-client', async (importOriginal) => ({ ...(await importOriginal<typeof import('../../lib/collector-client')>()), fetchRequests: vi.fn(), fetchRequestStory: vi.fn() }))
vi.mock('../../lib/request-answer', () => ({ postRequestAnswer: vi.fn() }))
vi.mock('../sessions/session-terminal', async (importOriginal) => ({ ...(await importOriginal<typeof import('../sessions/session-terminal')>()), openSessionTerminal: vi.fn() }))
function renderContent() { return render(<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}><RequestsContent /></QueryClientProvider>) }
function emptyStory(requestId: string) {
  return { story: {
    schemaVersion: 1 as const,
    requestId,
    title: 'Summary only',
    project: 'overdeck',
    state: 'asked',
    priority: 'NORMAL',
    askedAt: '2026-08-14T00:00:00.000Z',
    updatedAt: '2026-08-14T00:00:00.000Z',
    originalRequest: { body: null, format: null, source: null },
    events: [],
    links: [],
    attachments: [],
    coverage: [
      { fact: 'original_request' as const, status: 'unavailable' as const, sourceId: 'legacy-request-registry', reason: 'The complete original ask was not recorded for this request.' },
      { fact: 'activity' as const, status: 'unavailable' as const, sourceId: 'legacy-request-registry', reason: 'No typed activity was recorded for this request.' },
    ],
  } }
}
beforeEach(() => { vi.mocked(fetchRequestStory).mockImplementation(async (requestId) => emptyStory(requestId)) })
afterEach(() => { cleanup(); vi.clearAllMocks() })

describe('RequestsContent', () => {
  it('renders the board with live request rows sorted into columns', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [
        { id: 'req-a', title: 'Ship requests', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner', asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: 'https://proof.example/request', plan_ref: null },
      ],
    })
    renderContent()
    expect(await screen.findByRole('heading', { name: 'Requests' })).toBeInTheDocument()
    expect(screen.getByText('Ship requests')).toBeInTheDocument()
    expect(screen.getAllByText('overdeck').length).toBeGreaterThan(0)
    expect(screen.getByText('Lane: codex')).toBeInTheDocument()
  })
  it('opens a drawer with the exact original request and typed activity story', async () => {
    vi.mocked(fetchRequestStory).mockResolvedValue({ story: {
      schemaVersion: 1, requestId: 'req-history', title: 'Show history', project: 'overdeck', state: 'in_flight', priority: 'HIGH',
      askedAt: '2026-08-14T00:00:00.000Z', updatedAt: '2026-08-14T01:00:00.000Z',
      originalRequest: { body: 'Exact owner ask\nwith its second line.', format: 'plain_text', source: 'request-hook' },
      events: [{ id: 'event-1', requestId: 'req-history', kind: 'claimed', occurredAt: '2026-08-14T01:00:00.000Z', recordedAt: '2026-08-14T01:00:00.000Z', summary: 'Work started on the request.', actor: { type: 'agent', displayName: 'codex@buildbox-1' }, sourceId: 'request-registry', sourceEventId: 'source-event', producerId: 'request-registry', payload: {}, legacy: false }],
      links: [],
      attachments: [],
      coverage: [
        { fact: 'original_request', status: 'complete', sourceId: 'request-hook' },
        { fact: 'activity', status: 'complete', sourceId: 'request-events' },
        { fact: 'activity_delivery', status: 'pending_delivery', sourceId: 'claude-task-hook', reason: '2 recorded updates are waiting for delivery.' },
      ],
    } })
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{
        id: 'req-history', title: 'Show history', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
        asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: 'Checking the implementation.', proof_url: null, plan_ref: null,
        receipt_trail: [{ id: 'receipt-1', request_id: 'req-history', at: '2026-08-14T01:00:00.000Z', kind: 'claimed', line: 'Work started on the request.', meta: { worker: 'codex', host: 'buildbox-1' } }],
      }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('Show history'))
    const drawer = screen.getByRole('dialog', { name: 'Show history' })
    expect(drawer).toBeInTheDocument()
    expect(drawer).not.toHaveAttribute('aria-modal')
    expect(screen.getByText('What’s happening')).toBeInTheDocument()
    expect(await screen.findByText(/Exact owner ask\s+with its second line\./)).toBeInTheDocument()
    expect(fetchRequestStory).toHaveBeenCalledWith('req-history', expect.any(AbortSignal))
    expect(screen.getByText('Request hook · Complete')).toBeInTheDocument()
    expect(screen.getByText('Request history · Complete')).toBeInTheDocument()
    expect(screen.getByText('Claude task hook delivery · Waiting for delivery — 2 recorded updates are waiting for delivery.')).toBeInTheDocument()
    expect(within(drawer).getByText('Work started on the request.')).toBeInTheDocument()
    expect(screen.getByText('codex@buildbox-1')).toBeInTheDocument()
    expect(screen.getByText('Spec, branch, landing, and proof links were not recorded.')).toBeInTheDocument()
  })

  it('shows recorded worker loss as recovery work in the drawer', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{
        id: 'req-orphaned', title: 'Recover stopped work', project: 'overdeck', state: 'orphaned', priority: 'HIGH', origin: 'owner',
        asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T01:00:00.000Z', worker: 'Lifecycle worker',
        detail: 'Worker session stopped with 1 uncommitted file.', proof_url: null, plan_ref: null, receipt_trail: [],
      }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('Recover stopped work'))
    expect(screen.getByText('Worker session stopped with 1 uncommitted file. This request needs recovery.')).toBeInTheDocument()
    expect(screen.queryByText(/You do not need to act right now/)).not.toBeInTheDocument()
  })

  it('opens the session terminal when an actor with a known session is clicked', async () => {
    vi.mocked(fetchRequestStory).mockResolvedValue({ story: {
      ...emptyStory('req-clickable').story,
      events: [{ id: 'event-session', requestId: 'req-clickable', kind: 'claimed', occurredAt: '2026-08-14T01:00:00.000Z', recordedAt: '2026-08-14T01:00:00.000Z', summary: 'Work started on the request.', actor: { type: 'agent', displayName: 'codex@buildbox-1' }, sessionId: 'session-42', sourceId: 'request-registry', sourceEventId: 'source-event', producerId: 'request-registry', payload: {}, legacy: false }],
      links: [],
      attachments: [],
      coverage: [{ fact: 'original_request', status: 'unavailable', sourceId: 'legacy-request-registry' }, { fact: 'activity', status: 'complete', sourceId: 'request-events' }],
    } })
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{
        id: 'req-clickable', title: 'Click the actor', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
        asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: null, plan_ref: null,
        receipt_trail: [{ id: 'receipt-1', request_id: 'req-clickable', at: '2026-08-14T01:00:00.000Z', kind: 'claimed', line: 'Work started on the request.', meta: { worker: 'codex', host: 'buildbox-1', session_id: 'session-42' } }],
      }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('Click the actor'))
    fireEvent.click(await screen.findByRole('button', { name: 'codex@buildbox-1' }))
    expect(vi.mocked(openSessionTerminal)).toHaveBeenCalledWith('session-42')
  })

  it('links attributed factory evidence to the live activity surface', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({ requests: [{
      id: 'req-factory', title: 'Factory-backed request', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
      asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: '/factory/adw-42', plan_ref: 'agent-story', session_id: 'cli-session-1', receipt_trail: [],
    }] })
    renderContent()
    fireEvent.click(await screen.findByText('Factory-backed request'))
    expect(screen.getByRole('link', { name: 'Open activity' })).toHaveAttribute('href', '/factory/adw-42')
    expect(screen.queryByText('adw-42')).not.toBeInTheDocument()
  })

  it('does not guess a factory run from CLI session attribution', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({ requests: [{
      id: 'req-cli', title: 'CLI-backed request', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
      asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: null, plan_ref: null, session_id: 'cli-session-1', receipt_trail: [],
    }] })
    renderContent()
    fireEvent.click(await screen.findByText('CLI-backed request'))
    expect(screen.queryByRole('link', { name: 'Open activity' })).not.toBeInTheDocument()
  })

  it('renders an actor with no known session as plain text, never a dead link', async () => {
    vi.mocked(fetchRequestStory).mockResolvedValue({ story: {
      ...emptyStory('req-unlinked').story,
      events: [{ id: 'event-unlinked', requestId: 'req-unlinked', kind: 'claimed', occurredAt: '2026-08-14T01:00:00.000Z', recordedAt: '2026-08-14T01:00:00.000Z', summary: 'Work started on the request.', actor: { type: 'agent', displayName: 'codex@buildbox-1' }, sourceId: 'request-registry', sourceEventId: 'source-event', producerId: 'request-registry', payload: {}, legacy: false }],
      links: [],
      attachments: [],
      coverage: [{ fact: 'original_request', status: 'unavailable', sourceId: 'legacy-request-registry' }, { fact: 'activity', status: 'complete', sourceId: 'request-events' }],
    } })
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{
        id: 'req-unlinked', title: 'No session known', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
        asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: null, plan_ref: null,
        receipt_trail: [{ id: 'receipt-1', request_id: 'req-unlinked', at: '2026-08-14T01:00:00.000Z', kind: 'claimed', line: 'Work started on the request.', meta: { worker: 'codex', host: 'buildbox-1' } }],
      }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('No session known'))
    expect(await screen.findByText('codex@buildbox-1')).toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'codex@buildbox-1' })).not.toBeInTheDocument()
  })

  it('shows the honest shipped-without-proof gap in the drawer, never a stub link', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{
        id: 'req-shipped-no-proof', title: 'Shipped quietly', project: 'overdeck', state: 'shipped', priority: 'NORMAL', origin: 'owner',
        asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: 'codex', detail: null, proof_url: null, plan_ref: null, receipt_trail: [],
      }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('Shipped quietly'))
    expect(screen.getByText('This was marked shipped, but proof was not recorded. You do not need to act.')).toBeInTheDocument()
    expect(screen.queryByRole('link', { name: 'Deployed proof ↗' })).not.toBeInTheDocument()
  })

  it('renders the honest empty history state when the registry returns no receipts', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{ id: 'req-empty', title: 'Empty history', project: 'overdeck', state: 'asked', priority: 'NORMAL', origin: 'owner', asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: null, detail: null, proof_url: null, plan_ref: null, receipt_trail: [] }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('Empty history'))
    expect(await screen.findByText('No recorded activity yet.')).toBeInTheDocument()
  })

  it('renders source-specific unavailable coverage when typed activity does not exist', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({
      requests: [{ id: 'req-no-trail', title: 'No trail yet', project: 'overdeck', state: 'asked', priority: 'NORMAL', origin: 'owner', asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: null, detail: null, proof_url: null, plan_ref: null }],
    })
    renderContent()
    fireEvent.click(await screen.findByText('No trail yet'))
    expect(await screen.findByText('The full original request was not recorded. The card title is only a summary.')).toBeInTheDocument()
    expect(screen.getByText(/No typed activity was recorded for this request\./)).toBeInTheDocument()
  })

  it('answers blocked work only from the drawer and removes it after the journaled mutation succeeds', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({ requests: [{
      id: 'req-blocked', title: 'Choose release', project: 'overdeck', state: 'blocked_needs_owner', priority: 'HIGH', origin: 'owner',
      asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T00:00:00.000Z', worker: null, detail: 'Which release should ship?', proof_url: null, plan_ref: null, receipt_trail: [],
    }] })
    vi.mocked(postRequestAnswer).mockResolvedValue({ request: {
      id: 'req-blocked', title: 'Choose release', project: 'overdeck', state: 'in_flight', priority: 'HIGH', origin: 'owner',
      asked_at: '2026-08-14T00:00:00.000Z', updated_at: '2026-08-14T01:00:00.000Z', worker: null, detail: 'Ship canary', proof_url: null, plan_ref: null,
    }, factory_delivery: 'not-applicable' })
    renderContent()
    fireEvent.click(await screen.findByText('Choose release'))
    fireEvent.click(screen.getByRole('button', { name: 'Answer' }))
    fireEvent.change(screen.getByRole('textbox', { name: 'Answer for Choose release' }), { target: { value: 'Ship canary' } })
    fireEvent.click(screen.getByRole('button', { name: 'Send answer' }))
    await waitFor(() => expect(postRequestAnswer).toHaveBeenCalledWith('req-blocked', 'Ship canary'))
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
    expect(screen.getByText('Choose release')).toBeInTheDocument()
    expect(within(screen.getByRole('region', { name: /In flight/ })).getByText('Choose release')).toBeInTheDocument()
    expect(screen.getByText('Nothing needs you.')).toBeInTheDocument()
  })

  it('renders the calm board when no requests exist', async () => {
    vi.mocked(fetchRequests).mockResolvedValue({ requests: [] })
    renderContent()
    expect(await screen.findByText('Nothing in flight')).toBeInTheDocument()
    expect(screen.getByText('Ask for something.')).toBeInTheDocument()
    expect(screen.getByText('Nothing needs you.')).toBeInTheDocument()
  })
})
