/** @vitest-environment jsdom */
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { AttemptDetailDrawer } from './AttemptDetailDrawer'
import type { AttemptRecord } from './attempt-record'

function attempt(overrides: Partial<AttemptRecord> = {}): AttemptRecord {
  return {
    attemptId: 'attempt-done',
    task: 't1',
    phase: 'coder',
    seat: 'coder',
    model: 'gpt-test',
    wrapper: '/tmp/agent.sh',
    timeoutSecs: 120,
    promptPath: '/run/attempts/attempt-done.prompt.md',
    promptSha: 'a'.repeat(64),
    causalInput: 't1:initial',
    valid: true,
    replyPath: '/run/attempts/attempt-done.reply.json',
    usage: { inputTokens: 11, outputTokens: 7, model: 'gpt-test' },
    liveness: null,
    activity: {
      toolCalls: 6,
      filesTouched: ['src/app.js'],
      repeatedCommands: [['bash', 'npm test']],
      failureFingerprint: 'coder:coder:gpt-test',
    },
    ...overrides,
  }
}

function mockArtifactFetch(responses: Record<string, { status: number; body?: string }>) {
  vi.stubGlobal(
    'fetch',
    vi.fn(async (input: RequestInfo | URL) => {
      const url = String(input)
      const match = Object.entries(responses).find(([href]) => url === href)
      if (!match) {
        return new Response('', { status: 404 })
      }
      const [, response] = match
      return new Response(response.body ?? '', { status: response.status })
    }),
  )
}

describe('AttemptDetailDrawer', () => {
  afterEach(() => {
    vi.unstubAllGlobals()
    vi.clearAllMocks()
  })

  it('renders cost and cached tokens when measured usage includes them', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({
          usage: { inputTokens: 100, outputTokens: 50, model: 'gpt-test', costUsd: 0.0234, cachedTokens: 1200 },
        })}
        open
        onClose={vi.fn()}
      />,
    )

    const usage = screen.getByTestId('attempt-drawer-usage')
    expect(usage).toHaveTextContent('100 in · 50 out')
    expect(usage).toHaveTextContent('$0.0234')
    expect(usage).toHaveTextContent('1200 cached')
    expect(usage).toHaveTextContent('gpt-test')
  })

  it('omits cost and cached token lines when usage lacks them', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({
          usage: { inputTokens: 11, outputTokens: 7, model: 'gpt-test' },
        })}
        open
        onClose={vi.fn()}
      />,
    )

    const usage = screen.getByTestId('attempt-drawer-usage')
    expect(usage).toHaveTextContent('11 in · 7 out')
    expect(usage).not.toHaveTextContent('$')
    expect(usage).not.toHaveTextContent('cached')
  })

  it('renders verdict, measured usage, and activity counters', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({ verdict: 'PASS', failureClass: 'gate-failed' })}
        open
        onClose={vi.fn()}
      />,
    )

    expect(screen.getByTestId('attempt-drawer-verdict')).toHaveTextContent('WORKS')
    expect(screen.getByTestId('attempt-drawer-usage')).toHaveTextContent('11 in · 7 out')
    expect(screen.getByTestId('attempt-drawer-usage')).toHaveTextContent('gpt-test')
    expect(screen.getByTestId('attempt-drawer-failure-class')).toHaveTextContent('gate-failed')

    const activity = within(screen.getByTestId('attempt-drawer-activity'))
    expect(activity.getByText('Tool calls').nextElementSibling).toHaveTextContent('6')
    expect(activity.getByText('Files touched').nextElementSibling).toHaveTextContent('1')
    expect(activity.getByText('Repeated commands').nextElementSibling).toHaveTextContent('1')
    expect(activity.getByText('coder:coder:gpt-test')).toBeInTheDocument()
  })

  it('renders unmetered usage when usage is unknown', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({
          usage: { unknown: true, model: 'gpt-alt' },
          activity: null,
        })}
        open
        onClose={vi.fn()}
      />,
    )

    expect(screen.getByTestId('attempt-drawer-usage')).toHaveTextContent('unmetered')
    expect(screen.getByTestId('attempt-drawer-usage')).toHaveTextContent('gpt-alt')
  })

  it('shows honest unavailable states for in-progress attempts', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({
          valid: undefined,
          verdict: undefined,
          usage: undefined,
          activity: null,
          replyPath: undefined,
          liveness: {
            elapsedSecs: 3,
            lastActivity: 'thinking',
            lastSignalAt: '2026-08-04T02:59:57.000Z',
          },
        })}
        open
        onClose={vi.fn()}
      />,
    )

    expect(screen.getByTestId('attempt-drawer-verdict')).toHaveTextContent('running')
    expect(screen.getByTestId('attempt-drawer-usage')).toHaveTextContent('not yet available')
    expect(screen.getByTestId('attempt-drawer-activity')).toHaveTextContent('not yet available')
    expect(screen.getByTestId('attempt-drawer-reply-missing')).toHaveTextContent('not yet available')
  })

  it('omits failure class and fingerprint when absent', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt({
          failureClass: undefined,
          activity: { toolCalls: 2, filesTouched: [], repeatedCommands: [] },
        })}
        open
        onClose={vi.fn()}
      />,
    )

    expect(screen.queryByTestId('attempt-drawer-failure-class')).not.toBeInTheDocument()
    expect(screen.queryByText('Failure fingerprint')).not.toBeInTheDocument()
  })

  it('renders secondary reply link with the correct href', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/reply': { status: 200, body: '{"ok":true}' },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        replyHref="/api/runs/run-1/attempts/attempt-done/reply"
      />,
    )

    await waitFor(() => expect(screen.getByTestId('attempt-drawer-reply-content')).toBeInTheDocument())
    expect(screen.getByTestId('attempt-drawer-reply-link')).toHaveAttribute(
      'href',
      '/api/runs/run-1/attempts/attempt-done/reply',
    )
  })

  it('renders secondary prompt link with the correct href', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/prompt': { status: 200, body: 'compiled prompt body' },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        promptHref="/api/runs/run-1/attempts/attempt-done/prompt"
      />,
    )

    await waitFor(() => expect(screen.getByRole('button', { name: 'Expand prompt' })).toBeInTheDocument())
    expect(screen.getByTestId('attempt-drawer-prompt-link')).toHaveAttribute(
      'href',
      '/api/runs/run-1/attempts/attempt-done/prompt',
    )
  })

  it('expands prompt content on demand when provided via prop', () => {
    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        promptContent="full compiled prompt body"
      />,
    )

    expect(screen.queryByText('full compiled prompt body')).not.toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Expand prompt' }))
    expect(screen.getByText('full compiled prompt body')).toBeInTheDocument()
  })

  it('fetches and renders inline prompt content collapsed by default', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/prompt': { status: 200, body: 'fetched prompt body' },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        promptHref="/api/runs/run-1/attempts/attempt-done/prompt"
      />,
    )

    expect(screen.getByTestId('attempt-drawer-prompt-loading')).toBeInTheDocument()
    await waitFor(() => expect(screen.queryByTestId('attempt-drawer-prompt-loading')).not.toBeInTheDocument())
    expect(screen.queryByText('fetched prompt body')).not.toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Expand prompt' }))
    expect(screen.getByTestId('attempt-drawer-prompt-content')).toHaveTextContent('fetched prompt body')
  })

  it('fetches and renders inline reply content', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/reply': { status: 200, body: '{"message":"done"}' },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        replyHref="/api/runs/run-1/attempts/attempt-done/reply"
      />,
    )

    expect(screen.getByTestId('attempt-drawer-reply-loading')).toBeInTheDocument()
    await waitFor(() => expect(screen.getByTestId('attempt-drawer-reply-content')).toHaveTextContent('{"message":"done"}'))
  })

  it('does not show fetched artifacts from a previous attempt while a new attempt loads', async () => {
    const responses = new Map<string, (response: Response) => void>()
    vi.stubGlobal(
      'fetch',
      vi.fn((input: RequestInfo | URL) => new Promise<Response>((resolve) => responses.set(String(input), resolve))),
    )

    const view = render(
      <AttemptDetailDrawer
        attempt={attempt({ attemptId: 'attempt-a' })}
        open
        onClose={vi.fn()}
        promptHref="/attempt-a/prompt"
        replyHref="/attempt-a/reply"
      />,
    )

    await waitFor(() => expect(responses.size).toBe(2))
    responses.get('/attempt-a/prompt')!(new Response('prompt from attempt A', { status: 200 }))
    responses.get('/attempt-a/reply')!(new Response('reply from attempt A', { status: 200 }))
    await waitFor(() => expect(screen.getByTestId('attempt-drawer-reply-content')).toHaveTextContent('reply from attempt A'))
    fireEvent.click(screen.getByRole('button', { name: 'Expand prompt' }))
    expect(screen.getByTestId('attempt-drawer-prompt-content')).toHaveTextContent('prompt from attempt A')

    view.rerender(
      <AttemptDetailDrawer
        attempt={attempt({ attemptId: 'attempt-b' })}
        open
        onClose={vi.fn()}
        promptHref="/attempt-b/prompt"
        replyHref="/attempt-b/reply"
      />,
    )

    expect(screen.queryByText('prompt from attempt A')).not.toBeInTheDocument()
    expect(screen.queryByText('reply from attempt A')).not.toBeInTheDocument()
    expect(screen.getByTestId('attempt-drawer-prompt-loading')).toBeInTheDocument()
    expect(screen.getByTestId('attempt-drawer-reply-loading')).toBeInTheDocument()
  })

  it('shows not captured when prompt fetch fails', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/prompt': { status: 404 },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        promptHref="/api/runs/run-1/attempts/attempt-done/prompt"
      />,
    )

    await waitFor(() => expect(screen.getByTestId('attempt-drawer-prompt-not-captured')).toHaveTextContent('not captured'))
    expect(screen.getByTestId('attempt-drawer-prompt-link')).toBeInTheDocument()
  })

  it('shows not captured when reply fetch fails', async () => {
    mockArtifactFetch({
      '/api/runs/run-1/attempts/attempt-done/reply': { status: 500 },
    })

    render(
      <AttemptDetailDrawer
        attempt={attempt()}
        open
        onClose={vi.fn()}
        replyHref="/api/runs/run-1/attempts/attempt-done/reply"
      />,
    )

    await waitFor(() => expect(screen.getByTestId('attempt-drawer-reply-not-captured')).toHaveTextContent('not captured'))
    expect(screen.getByTestId('attempt-drawer-reply-link')).toBeInTheDocument()
  })

  it('returns null when closed', () => {
    const { container } = render(
      <AttemptDetailDrawer attempt={attempt()} open={false} onClose={vi.fn()} />,
    )
    expect(container).toBeEmptyDOMElement()
  })
})
