/** @vitest-environment jsdom */
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
  CollectorHttpError,
  fetchCollectorItems,
  fetchCollectorState,
  fetchHarnessAttempts,
  fetchHarnessConfig,
  fetchHarnessDecisions,
  fetchHarnessEvents,
  fetchHarnessPlan,
  fetchHarnessRunDetail,
} from '../../lib/collector-client'
import type { StateResponse } from '../../lib/collector-types'
import type { ForensicsPanelData, HarnessPlanRun } from '../../lib/panel-data'
import type { AttemptRecord } from './attempt-record'
import type { HarnessEvent } from '../../lib/harness-types'
import { PlanRunApp } from './PlanRunApp'

vi.mock('../../lib/collector-client', async (importOriginal) => ({
  ...await importOriginal<typeof import('../../lib/collector-client')>(),
  fetchCollectorState: vi.fn(),
  fetchCollectorItems: vi.fn(),
  fetchHarnessConfig: vi.fn(),
  fetchHarnessEvents: vi.fn(),
  fetchHarnessPlan: vi.fn(),
  fetchHarnessDecisions: vi.fn(),
  fetchHarnessRunDetail: vi.fn(),
  fetchHarnessAttempts: vi.fn(),
}))

vi.mock('../overview/CollectorRealtimeBridge', () => ({
  CollectorRealtimeBridge: () => null,
}))

const run: HarnessPlanRun = {
  runId: 'run-28',
  seq: 28,
  title: 'Optimize data integrity',
  status: 'running',
  state: 'executing',
  owner: 'operator',
  currentTask: 't4',
  tasksTotal: 2,
  tasksCompleted: 1,
  pendingDecisions: 0,
  degradedReason: '',
  updatedAt: '2026-07-18T12:00:00.000Z',
  repoRoot: '/home/user/Projects/overdeck',
  abandoned: false,
  abandonedAt: null,
  waves: [
    {
      wave: 1,
      tasks: [
        { id: 't1', status: 'succeeded' },
        { id: 't4', status: 'running' },
      ],
    },
  ],
}

const forensics: ForensicsPanelData = {
  tiles: [],
  attribution: [],
  runs: [],
  segments: [],
}

function attemptFixture(overrides: Partial<AttemptRecord> = {}): AttemptRecord {
  return {
    attemptId: 'attempt-done',
    task: 't4',
    phase: 'coder',
    seat: 'coder',
    model: 'gpt-test',
    wrapper: '/tmp/agent.sh',
    timeoutSecs: 120,
    promptPath: '/run/attempts/attempt-done.prompt.md',
    valid: true,
    replyPath: '/run/attempts/attempt-done.reply.json',
    usage: { inputTokens: 11, outputTokens: 7, model: 'gpt-test' },
    liveness: null,
    activity: { toolCalls: 2 },
    ...overrides,
  }
}

function mockHarnessQueries() {
  vi.mocked(fetchHarnessConfig).mockResolvedValue({
    revision: 'r1',
    fields: {},
  })
  vi.mocked(fetchHarnessEvents).mockResolvedValue({
    events: [],
    nextSince: 'opaque:0001',
    capabilities: {},
  })
  vi.mocked(fetchHarnessPlan).mockResolvedValue({
    runId: 'run-28',
    revision: 'r1',
    planHash: 'hash',
    tasks: [],
    meta: { task_dispatch_budget: 8 },
  })
  vi.mocked(fetchHarnessDecisions).mockResolvedValue({
    decisions: [],
    capabilities: {},
  })
  vi.mocked(fetchHarnessRunDetail).mockResolvedValue({ run: {} })
}

function mockCollectorState() {
  const state: StateResponse = {
    panels: [
      { id: 'plans', ts: '2026-07-22T00:00:00.000Z', data: { runs: [run] } },
      { id: 'forensics:run-28', ts: '2026-07-22T00:00:00.000Z', data: forensics },
    ],
    adapters: [],
  }
  vi.mocked(fetchCollectorState).mockResolvedValue(state)
  vi.mocked(fetchCollectorItems).mockResolvedValue({ items: [] })
}

describe('PlanRunApp attempt history', () => {
  afterEach(() => {
    vi.clearAllMocks()
  })

  it('renders attempt history when the fetch succeeds', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([
      attemptFixture({ attemptId: 'attempt-a' }),
      attemptFixture({ attemptId: 'attempt-b', model: 'gpt-alt' }),
    ])

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() => expect(screen.getByTestId('attempt-history-panel')).toBeInTheDocument())
    expect(screen.getByTestId('attempt-entry-attempt-a')).toBeInTheDocument()
    expect(screen.getByTestId('attempt-entry-attempt-b')).toBeInTheDocument()
    expect(screen.queryByTestId('autopsy-strip')).not.toBeInTheDocument()
  })

  it('opens compiled prompt artifact from selected attempt', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([attemptFixture({ attemptId: 'attempt-prompt' })])

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() => expect(screen.getByTestId('attempt-entry-attempt-prompt')).toBeInTheDocument())
    fireEvent.click(screen.getByTestId('attempt-entry-attempt-prompt'))
    expect(screen.getByTestId('attempt-drawer-prompt-link')).toHaveAttribute(
      'href',
      '/api/collector/harness/runs/run-28/attempts/attempt-prompt/prompt',
    )
  })

  it('renders an honest not-captured state when the fetch returns empty', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([])

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() => expect(screen.getByTestId('attempt-history-not-captured')).toBeInTheDocument())
    expect(screen.getByTestId('attempt-history-not-captured')).toHaveTextContent('Attempt history not captured for this run.')
    expect(screen.queryByTestId('attempt-history-panel')).not.toBeInTheDocument()
  })

  it('renders an honest not-captured state when the fetch fails', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockRejectedValue(new CollectorHttpError(500, null, 'collector request failed'))

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() => expect(screen.getByTestId('attempt-history-not-captured')).toBeInTheDocument())
    expect(screen.getByTestId('attempt-history-not-captured')).toHaveTextContent('Attempt history could not be loaded for this run.')
    expect(screen.queryByTestId('attempt-history-panel')).not.toBeInTheDocument()
  })
})

describe('PlanRunApp attention: task failures', () => {
  afterEach(() => {
    vi.clearAllMocks()
  })

  it('renders an attention item for a failed task with a failure reason', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([])
    const failedRun: HarnessPlanRun = {
      ...run,
      waves: [{ wave: 1, tasks: [{ id: 't1', status: 'succeeded' }, { id: 't4', status: 'failed' }] }],
    }
    const state: StateResponse = {
      panels: [
        { id: 'plans', ts: '2026-07-22T00:00:00.000Z', data: { runs: [failedRun] } },
        { id: 'forensics:run-28', ts: '2026-07-22T00:00:00.000Z', data: forensics },
      ],
      adapters: [],
    }
    vi.mocked(fetchCollectorState).mockResolvedValue(state)
    const events: HarnessEvent[] = [
      {
        id: 'e1',
        source: 'harness',
        kind: 'verify.failed',
        ts: '2026-07-18T12:00:00.000Z',
        taskId: 't4',
        payload: { command: 'pnpm test' },
      },
    ]
    vi.mocked(fetchHarnessEvents).mockResolvedValue({ events, nextSince: 'opaque:0002', capabilities: {} })

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() =>
      expect(document.querySelector('[data-attention-row="task-failure-t4"]')).toBeInTheDocument(),
    )
  })

  it('renders an attention item for a gated task with a failure reason', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([])
    const gatedRun: HarnessPlanRun = {
      ...run,
      waves: [{ wave: 1, tasks: [{ id: 't1', status: 'succeeded' }, { id: 't4', status: 'gated' }] }],
    }
    const state: StateResponse = {
      panels: [
        { id: 'plans', ts: '2026-07-22T00:00:00.000Z', data: { runs: [gatedRun] } },
        { id: 'forensics:run-28', ts: '2026-07-22T00:00:00.000Z', data: forensics },
      ],
      adapters: [],
    }
    vi.mocked(fetchCollectorState).mockResolvedValue(state)
    const events: HarnessEvent[] = [
      {
        id: 'e1',
        source: 'harness',
        kind: 'verify.failed',
        ts: '2026-07-18T12:00:00.000Z',
        taskId: 't4',
        payload: { command: 'pnpm test' },
      },
    ]
    vi.mocked(fetchHarnessEvents).mockResolvedValue({ events, nextSince: 'opaque:0002', capabilities: {} })

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() =>
      expect(document.querySelector('[data-attention-row="task-failure-t4"]')).toBeInTheDocument(),
    )
  })

  it('does not render an attention item for a running task even with a reason in the map', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([])
    const events: HarnessEvent[] = [
      {
        id: 'e1',
        source: 'harness',
        kind: 'verify.failed',
        ts: '2026-07-18T12:00:00.000Z',
        taskId: 't4',
        payload: { command: 'pnpm test' },
      },
    ]
    vi.mocked(fetchHarnessEvents).mockResolvedValue({ events, nextSince: 'opaque:0002', capabilities: {} })

    render(<PlanRunApp runId="run-28" />)

    await waitFor(() => expect(screen.getByTestId('attempt-history-not-captured')).toBeInTheDocument())
    expect(document.querySelector('[data-attention-row="task-failure-t4"]')).not.toBeInTheDocument()
  })

  it('renders no task-failure item and does not throw when the events query is undefined', async () => {
    mockCollectorState()
    mockHarnessQueries()
    vi.mocked(fetchHarnessAttempts).mockResolvedValue([])
    vi.mocked(fetchHarnessEvents).mockRejectedValue(new CollectorHttpError(500, null, 'collector request failed'))

    expect(() => render(<PlanRunApp runId="run-28" />)).not.toThrow()

    await waitFor(() => expect(screen.getByTestId('attempt-history-not-captured')).toBeInTheDocument())
    expect(document.querySelector('[data-attention-row^="task-failure-"]')).not.toBeInTheDocument()
  })
})
