// @vitest-environment jsdom
import '@testing-library/jest-dom'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'

const { mockPreviewEnqueueAllAction, mockEnqueueClaudeJobsBatchAction } = vi.hoisted(() => ({
  mockPreviewEnqueueAllAction: vi.fn(),
  mockEnqueueClaudeJobsBatchAction: vi.fn(),
}))

vi.mock('@/actions/claude-jobs', () => ({
  previewEnqueueAllAction: mockPreviewEnqueueAllAction,
  enqueueClaudeJobsBatchAction: mockEnqueueClaudeJobsBatchAction,
  cancelClaudeJobAction: vi.fn(),
  enqueueClaudeJobAction: vi.fn(),
}))
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn(), info: vi.fn() } }))
vi.mock('@dnd-kit/core', () => ({
  DndContext: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  DragOverlay: () => null,
  PointerSensor: class {},
  useSensor: vi.fn(() => ({})),
  useSensors: vi.fn(() => []),
  closestCorners: vi.fn(),
}))
vi.mock('@/components/ui/button', () => ({
  Button: ({
    children,
    onClick,
    disabled,
  }: {
    children?: React.ReactNode
    onClick?: () => void
    disabled?: boolean
  }) => (
    <button onClick={onClick} disabled={disabled}>
      {children}
    </button>
  ),
}))
vi.mock('@/components/ui/dialog', () => ({
  Dialog: ({ open, children }: { open: boolean; children: React.ReactNode }) =>
    open ? <div data-testid="dialog">{children}</div> : null,
  DialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  DialogHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  DialogTitle: ({ children }: { children: React.ReactNode }) => <h2>{children}</h2>,
}))
vi.mock('@/components/ui/tooltip', () => ({
  TooltipProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  TooltipTrigger: ({ render: r, children }: { render?: React.ReactElement; children?: React.ReactNode }) =>
    r ? <>{r}</> : <>{children}</>,
  TooltipContent: () => null,
}))
vi.mock('@/components/shared/demo-tooltip', () => ({
  DemoTooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}))
vi.mock('@/components/split-pane/split-pane', () => ({
  SplitPane: ({ panes }: { panes: React.ReactNode[] }) => <>{panes}</>,
}))
vi.mock('@/components/solo/solo-column', () => ({
  SoloColumn: () => <div data-testid="solo-column" />,
}))
vi.mock('@/components/solo/solo-task-card', () => ({
  SoloTaskCardOverlay: () => null,
}))
vi.mock('@/components/solo/task-detail-dialog', () => ({
  TaskDetailDialog: () => null,
}))
vi.mock('@/components/solo/unassigned-stories-sheet', () => ({
  UnassignedStoriesSheet: () => null,
}))
vi.mock('@/lib/task-status', () => ({
  taskStatusToApi: (s: string) => s.toLowerCase(),
}))

import { useSoloStore } from '@/stores/solo-store'
import { SoloBoard } from '@/components/solo/solo-board'
import { toast } from 'sonner'

const PRODUCT_ID = 'prod-1'
const TODO_TASK = {
  id: 't1',
  title: 'Task 1',
  description: null,
  implementation_plan: null,
  priority: 1,
  sort_order: 1,
  status: 'TO_DO' as const,
  verify_only: false,
  verify_required: 'ALIGNED_OR_PARTIAL' as const,
  story_id: 'story-1',
  story_code: 'ST-1',
  story_title: 'Story 1',
  task_code: 'ST-1.1',
  pbi_code: null,
  pbi_title: null,
  pbi_description: null,
}

const DEFAULT_PROPS = {
  productId: PRODUCT_ID,
  sprintGoal: 'Sprint goal',
  tasks: [TODO_TASK],
  unassignedStories: [],
  isDemo: false,
  currentUserId: 'user-1',
}

const PREVIEW_NO_BLOCKER = {
  tasks: [{ id: 't1', title: 'Task 1', status: 'TO_DO', story_title: 'Story 1', pbi_id: 'pbi-1', pbi_status: 'READY' }],
  blockerIndex: null,
  blockerReason: null,
}

const PREVIEW_WITH_BLOCKER = {
  tasks: [
    { id: 't1', title: 'Task 1', status: 'TO_DO', story_title: 'Story 1', pbi_id: 'pbi-1', pbi_status: 'READY' },
    { id: 't2', title: 'Task 2', status: 'TO_DO', story_title: 'Story 1', pbi_id: 'pbi-1', pbi_status: 'READY' },
    { id: 't3', title: 'Task Review', status: 'REVIEW', story_title: 'Story 1', pbi_id: 'pbi-1', pbi_status: 'READY' },
  ],
  blockerIndex: 2,
  blockerReason: 'task-review' as const,
}

beforeEach(() => {
  vi.clearAllMocks()
  useSoloStore.setState({ tasks: {}, claudeJobsByTaskId: {}, connectedWorkers: 1 })
})

describe('SoloBoard — batch-enqueue flow', () => {
  it('no blocker: calls enqueueClaudeJobsBatchAction with TO_DO task IDs directly', async () => {
    mockPreviewEnqueueAllAction.mockResolvedValue(PREVIEW_NO_BLOCKER)
    mockEnqueueClaudeJobsBatchAction.mockResolvedValue({ success: true, count: 1 })

    render(<SoloBoard {...DEFAULT_PROPS} />)

    fireEvent.click(screen.getByText(/Start agents/))

    await waitFor(() => {
      expect(mockPreviewEnqueueAllAction).toHaveBeenCalledWith(PRODUCT_ID)
      expect(mockEnqueueClaudeJobsBatchAction).toHaveBeenCalledWith(PRODUCT_ID, ['t1'])
      expect(toast.success).toHaveBeenCalledWith(expect.stringContaining('1 agent'))
    })
  })

  it('blocker: shows dialog when preview returns blockerIndex', async () => {
    mockPreviewEnqueueAllAction.mockResolvedValue(PREVIEW_WITH_BLOCKER)

    render(<SoloBoard {...DEFAULT_PROPS} />)

    fireEvent.click(screen.getByText(/Start agents/))

    await waitFor(() => {
      expect(screen.getByTestId('dialog')).toBeInTheDocument()
      expect(screen.getByText(/Blokkade gedetecteerd/)).toBeInTheDocument()
    })
    expect(mockEnqueueClaudeJobsBatchAction).not.toHaveBeenCalled()
  })

  it('blocker dialog confirm: enqueues prefix tasks and closes', async () => {
    mockPreviewEnqueueAllAction.mockResolvedValue(PREVIEW_WITH_BLOCKER)
    mockEnqueueClaudeJobsBatchAction.mockResolvedValue({ success: true, count: 2 })

    render(<SoloBoard {...DEFAULT_PROPS} />)
    fireEvent.click(screen.getByText(/Start agents/))

    await waitFor(() => screen.getByTestId('dialog'))

    fireEvent.click(screen.getByText(/Stuur 2 taken tot aan blokkade/))

    await waitFor(() => {
      expect(mockEnqueueClaudeJobsBatchAction).toHaveBeenCalledWith(PRODUCT_ID, ['t1', 't2'])
      expect(toast.success).toHaveBeenCalledWith(expect.stringContaining('2 agents'))
      expect(screen.queryByTestId('dialog')).not.toBeInTheDocument()
    })
  })

  it('blocker dialog cancel: closes dialog without enqueuing', async () => {
    mockPreviewEnqueueAllAction.mockResolvedValue(PREVIEW_WITH_BLOCKER)

    render(<SoloBoard {...DEFAULT_PROPS} />)
    fireEvent.click(screen.getByText(/Start agents/))

    await waitFor(() => screen.getByTestId('dialog'))

    fireEvent.click(screen.getByText('Annuleer'))

    await waitFor(() => {
      expect(screen.queryByTestId('dialog')).not.toBeInTheDocument()
    })
    expect(mockEnqueueClaudeJobsBatchAction).not.toHaveBeenCalled()
  })

  it('preview error: shows toast without opening dialog', async () => {
    mockPreviewEnqueueAllAction.mockResolvedValue({ error: 'Geen toegang' })

    render(<SoloBoard {...DEFAULT_PROPS} />)
    fireEvent.click(screen.getByText(/Start agents/))

    await waitFor(() => {
      expect(toast.error).toHaveBeenCalledWith('Geen toegang')
    })
    expect(screen.queryByTestId('dialog')).not.toBeInTheDocument()
  })
})
