// @vitest-environment happy-dom
import '@testing-library/jest-dom/vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { vi, describe, it, expect, beforeEach } from 'vitest'
import * as parseModule from '@/lib/parse-agent-output'
import { AgentOutputViewer } from './AgentOutputViewer'

const EMPTY_CHUNKS: never[] = []
vi.mock('@/lib/trpc', () => ({
  trpc: {
    getAgentOutput: {
      useQuery: vi.fn(() => ({ data: EMPTY_CHUNKS, isLoading: false })),
    },
    onAgentOutput: {
      useSubscription: vi.fn(),
    },
  },
}))

vi.mock('@/hooks', () => ({
  useSubscriptionWithErrorHandling: vi.fn(() => ({
    error: null,
    isConnecting: false,
  })),
}))

function makeToolResultMessage(content: string) {
  return {
    type: 'tool_result' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
  }
}

function makeSystemMessage(content: string) {
  return {
    type: 'system' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
  }
}

function makeTextMessage(content: string) {
  return {
    type: 'text' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
  }
}

function makeToolCallMessage(content: string, toolName: string) {
  return {
    type: 'tool_call' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
    meta: { toolName },
  }
}

function makeTodoWriteMessage(todos: Array<{ content: string; status: string; activeForm: string }>) {
  return {
    type: 'tool_call' as const,
    content: 'TodoWrite(...)',
    timestamp: new Date('2024-01-01T00:00:00Z'),
    meta: {
      toolName: 'TodoWrite',
      toolInput: { todos },
    },
  }
}

function makeToolResultMessageWithMeta(
  content: string,
  meta: { toolName?: string; toolInput?: unknown }
) {
  return {
    type: 'tool_result' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
    meta,
  }
}

function makeErrorMessage(content: string) {
  return {
    type: 'error' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
  }
}

function makeSessionEndMessage(content: string) {
  return {
    type: 'session_end' as const,
    content,
    timestamp: new Date('2024-01-01T00:00:00Z'),
  }
}

describe('AgentOutputViewer', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // Default: no messages
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([])
  })

  // Test 1: tool_result renders collapsed by default
  it('renders tool_result collapsed by default', () => {
    const content = 'file content here and more stuff'
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeToolResultMessage(content)])

    render(<AgentOutputViewer agentId="agent-1" />)

    // ChevronRight should be present (collapsed state)
    // We check that the SVG for ChevronRight is in the document
    // lucide-react renders SVGs — we look for the collapsed container containing the preview text
    expect(screen.getByText(content.substring(0, 80))).toBeInTheDocument()

    // "Result" badge should NOT be visible (collapsed)
    expect(screen.queryByText('Result')).not.toBeInTheDocument()

    // The container should NOT show a "Result" badge
    // ChevronRight is rendered — verify no ChevronDown
    const svgs = document.querySelectorAll('svg')
    // We look for the collapsed state by absence of "Result" text
    expect(screen.queryByText('Result')).toBeNull()
  })

  // Test 2: Clicking collapsed result expands it
  it('expands tool_result on click', () => {
    const longContent = 'a'.repeat(100)
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeToolResultMessage(longContent)])

    render(<AgentOutputViewer agentId="agent-1" />)

    // Initially collapsed — click it
    const collapsedContainer = screen.getByText(longContent.substring(0, 80)).closest('div')!
    fireEvent.click(collapsedContainer)

    // After click: "Result" badge should be visible
    expect(screen.getByText('Result')).toBeInTheDocument()

    // Full content should be visible in whitespace-pre-wrap element
    const preWrap = document.querySelector('.whitespace-pre-wrap')
    expect(preWrap).toBeInTheDocument()
    expect(preWrap).toHaveTextContent(longContent)

    // ChevronRight should no longer be visible; ChevronDown should be present
    expect(screen.queryByText('Result')).toBeInTheDocument()
  })

  // Test 3: Clicking expanded result collapses it again
  it('collapses tool_result on second click', () => {
    const longContent = 'b'.repeat(100)
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeToolResultMessage(longContent)])

    render(<AgentOutputViewer agentId="agent-1" />)

    // Click once to expand
    const container = screen.getByText(longContent.substring(0, 80)).closest('div')!
    fireEvent.click(container)
    expect(screen.getByText('Result')).toBeInTheDocument()

    // Click again to collapse
    // After expansion, the container still exists — click the expanded container
    // The clickable container is the border-l-2 div
    const expandedContainer = screen.getByText('Result').closest('.border-l-2')!
    fireEvent.click(expandedContainer)

    // Should be collapsed again
    expect(screen.queryByText('Result')).not.toBeInTheDocument()
    expect(screen.getByText(longContent.substring(0, 80))).toBeInTheDocument()
  })

  // Test 4: system message renders as single dim line, no badge, no border-l
  it('renders system message as a single dim line without badge or border', () => {
    const content = 'Session started: abc-123'
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeSystemMessage(content)])

    render(<AgentOutputViewer agentId="agent-1" />)

    // Content is visible
    expect(screen.getByText(content)).toBeInTheDocument()

    // No "System" badge text
    expect(screen.queryByText('System')).not.toBeInTheDocument()

    // The rendered element should NOT have border-l class
    const el = screen.getByText(content)
    expect(el.className).not.toContain('border-l')
    expect(el.closest('[class*="border-l"]')).toBeNull()
  })

  // Test 5: agentId prop change resets expanded results
  it('resets expanded results when agentId changes', () => {
    const content = 'c'.repeat(100)
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeToolResultMessage(content)])

    const { rerender } = render(<AgentOutputViewer agentId="agent-1" />)

    // Expand the result
    const collapsedContainer = screen.getByText(content.substring(0, 80)).closest('div')!
    fireEvent.click(collapsedContainer)
    expect(screen.getByText('Result')).toBeInTheDocument()

    // Change agentId — should reset expandedResults
    rerender(<AgentOutputViewer agentId="agent-2" />)

    // After agentId change, result should be collapsed again
    expect(screen.queryByText('Result')).not.toBeInTheDocument()
    // Preview text should be visible (collapsed state)
    expect(screen.getByText(content.substring(0, 80))).toBeInTheDocument()
  })

  // Test 6: Other message types remain always-expanded (unaffected)
  it('always renders text messages fully', () => {
    const content = 'This is a text message'
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeTextMessage(content)])

    render(<AgentOutputViewer agentId="agent-1" />)

    expect(screen.getByText(content)).toBeInTheDocument()

    // No chevron icons for text messages
    const svgCount = document.querySelectorAll('svg').length
    // Only the header bar icons (Pause) should be present, no expand/collapse chevrons
    expect(screen.queryByText('Result')).not.toBeInTheDocument()
    expect(screen.queryByText('System')).not.toBeInTheDocument()
  })

  it('always renders tool_call messages fully', () => {
    const content = 'Read(file.txt)'
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeToolCallMessage(content, 'Read')])

    render(<AgentOutputViewer agentId="agent-1" />)

    expect(screen.getByText(content)).toBeInTheDocument()
    // The tool name badge should be visible
    expect(screen.getByText('Read')).toBeInTheDocument()
  })

  it('always renders error messages with Error badge', () => {
    const content = 'Something went wrong'
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeErrorMessage(content)])

    render(<AgentOutputViewer agentId="agent-1" />)

    expect(screen.getByText('Error')).toBeInTheDocument()
    expect(screen.getByText(content)).toBeInTheDocument()
  })

  it('always renders session_end messages with session completed text', () => {
    vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([makeSessionEndMessage('Session completed')])

    render(<AgentOutputViewer agentId="agent-1" />)

    expect(screen.getByText('Session completed')).toBeInTheDocument()
  })

  describe('Todo strip', () => {
    it('is absent when no TodoWrite tool_call exists', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTextMessage('some output'),
        makeToolCallMessage('Read(file.txt)', 'Read'),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.queryByText('TASKS')).not.toBeInTheDocument()
    })

    it('is present with TASKS label and all todos when a TodoWrite tool_call exists', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage([
          { content: 'Fix bug', status: 'completed', activeForm: 'Fixing bug' },
          { content: 'Add tests', status: 'in_progress', activeForm: 'Adding tests' },
          { content: 'Update docs', status: 'pending', activeForm: 'Updating docs' },
        ]),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.getByText('TASKS')).toBeInTheDocument()
      expect(screen.getByText('Fix bug')).toBeInTheDocument()
      expect(screen.getByText('Add tests')).toBeInTheDocument()
      expect(screen.getByText('Update docs')).toBeInTheDocument()
    })

    it('shows only the most recent TodoWrite todos', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage([
          { content: 'Old task', status: 'pending', activeForm: 'Old task' },
        ]),
        makeTodoWriteMessage([
          { content: 'New task', status: 'in_progress', activeForm: 'New task' },
        ]),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.getByText('New task')).toBeInTheDocument()
      expect(screen.queryByText('Old task')).not.toBeInTheDocument()
    })

    it('renders Loader2 with animate-spin for in_progress todo', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage([
          { content: 'Work', status: 'in_progress', activeForm: 'Working' },
        ]),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      // Find SVG with animate-spin class (Loader2)
      const spinningIcon = document.querySelector('svg.animate-spin')
      expect(spinningIcon).toBeInTheDocument()
    })

    it('renders CheckCircle2 and strikethrough text for completed todo', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage([
          { content: 'Done', status: 'completed', activeForm: 'Done' },
        ]),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      const doneText = screen.getByText('Done')
      expect(doneText.className).toContain('line-through')
    })

    it('renders Circle and muted text without strikethrough for pending todo', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage([
          { content: 'Later', status: 'pending', activeForm: 'Later' },
        ]),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      const laterText = screen.getByText('Later')
      expect(laterText.className).not.toContain('line-through')
    })

    it('renders at most 5 todo rows and shows overflow count when there are 7 todos', () => {
      const todos = Array.from({ length: 7 }, (_, i) => ({
        content: `Task ${i + 1}`,
        status: 'pending',
        activeForm: `Task ${i + 1}`,
      }))
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeTodoWriteMessage(todos),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      // Only first 5 are rendered
      expect(screen.getByText('Task 1')).toBeInTheDocument()
      expect(screen.getByText('Task 5')).toBeInTheDocument()
      expect(screen.queryByText('Task 6')).not.toBeInTheDocument()
      expect(screen.queryByText('Task 7')).not.toBeInTheDocument()

      // Overflow indicator
      expect(screen.getByText('+ 2 more')).toBeInTheDocument()
    })
  })

  describe('Task result collapsed preview', () => {
    it('shows subagent_type result label when meta.toolName is Task with subagent_type', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeToolResultMessageWithMeta('raw subagent output content '.repeat(5), {
          toolName: 'Task',
          toolInput: { subagent_type: 'Explore', description: 'desc', prompt: 'prompt' },
        }),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.getByText('Explore result')).toBeInTheDocument()
    })

    it('shows Subagent result when meta.toolName is Task but no subagent_type', () => {
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeToolResultMessageWithMeta('raw subagent output content '.repeat(5), {
          toolName: 'Task',
          toolInput: { description: 'desc', prompt: 'prompt' },
        }),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.getByText('Subagent result')).toBeInTheDocument()
    })

    it('shows first 80 chars of content for non-Task tool results', () => {
      const content = 'some file content that is longer than 80 characters '.repeat(3)
      vi.spyOn(parseModule, 'parseAgentOutput').mockReturnValue([
        makeToolResultMessageWithMeta(content, {
          toolName: 'Read',
        }),
      ])

      render(<AgentOutputViewer agentId="agent-1" />)

      expect(screen.getByText(content.substring(0, 80))).toBeInTheDocument()
    })
  })
})
