import { render, screen, fireEvent } from '@testing-library/react'
import { GanttTimelineView } from '../GanttTimelineView'
import type { GanttTimelineViewProps } from '../GanttTimelineView'
import type { GanttTask, TimelineItem } from '../types'
import { ZOOM_LEVELS, ROW_HEIGHT } from '../hooks/useGanttState'
import React from 'react'

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeItem(overrides: Partial<TimelineItem> & { id: string; title: string; status: string }): TimelineItem {
  return { startDate: '2025-01-01', endDate: '2025-03-31', ...overrides }
}

function makeTask(item: TimelineItem, overrides: Partial<GanttTask> = {}): GanttTask {
  const start = new Date('2025-01-01')
  const end = new Date('2025-03-31')
  return {
    item,
    start,
    end,
    progress: 50,
    timeProgress: 30,
    depth: 0,
    hasChildren: false,
    parentId: null,
    healthStatus: 'on_track',
    ...overrides,
  }
}

// ---------------------------------------------------------------------------
// Minimal default props factory
// ---------------------------------------------------------------------------

function defaultProps(overrides: Partial<GanttTimelineViewProps> = {}): GanttTimelineViewProps {
  const ZOOM_INDEX = 3
  const DAY_WIDTH = ZOOM_LEVELS[ZOOM_INDEX]
  const startDate = new Date('2025-01-01')
  const days: Date[] = []
  for (let i = 0; i < 90; i++) {
    const d = new Date(startDate)
    d.setDate(d.getDate() + i)
    days.push(d)
  }

  const months = [
    { month: new Date('2025-01-01'), days: 31, label: 'Jan 2025' },
    { month: new Date('2025-02-01'), days: 28, label: 'Feb 2025' },
    { month: new Date('2025-03-01'), days: 31, label: 'Mar 2025' },
  ]

  const timelineWidth = days.length * DAY_WIDTH

  return {
    infoColumnWidth: 320,
    infoColumnLabel: 'Item',
    showCategory: true,
    showStatus: true,
    showFullscreen: false,
    zoomIndex: ZOOM_INDEX,
    setZoomIndex: jest.fn(),
    collapsed: new Set<string>(),
    mounted: true,
    dragState: null,
    focusedRowIndex: -1,
    dayWidth: DAY_WIDTH,
    tasks: [],
    startDate,
    days,
    months,
    timeHeaderUnits: {
      mode: 'days',
      units: days.map((day) => ({
        date: day,
        width: DAY_WIDTH,
        label: String(day.getDate()),
        isWeekend: day.getDay() === 0 || day.getDay() === 6,
        isToday: false,
      })),
    },
    dependencyPaths: [],
    timelineWidth,
    bodyHeight: 0,
    isFullscreen: false,
    categoryColors: { product: '#3b82f6', team: '#a855f7', other: '#6b7280' },
    onDateChange: undefined,
    bodyScrollRef: { current: null },
    headerTimelineRef: { current: null },
    infoColumnRef: { current: null },
    toggleFullscreen: jest.fn(),
    handleItemClick: jest.fn(),
    toggleCollapse: jest.fn(),
    handleBodyScroll: jest.fn(),
    getBarPosition: jest.fn().mockReturnValue({ left: 0, width: 100, top: 0, height: 20 }),
    handleDragStart: jest.fn(),
    ...overrides,
  }
}

// ---------------------------------------------------------------------------
// Header rendering
// ---------------------------------------------------------------------------

describe('GanttTimelineView — header', () => {
  it('renders the info column header with the infoColumnLabel', () => {
    render(<GanttTimelineView {...defaultProps({ infoColumnLabel: 'Goal' })} />)
    expect(screen.getByText('Goal')).toBeInTheDocument()
  })

  it('renders month labels in the timeline header', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByText('Jan 2025')).toBeInTheDocument()
    expect(screen.getByText('Feb 2025')).toBeInTheDocument()
    expect(screen.getByText('Mar 2025')).toBeInTheDocument()
  })

  it('renders day unit labels in days mode', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    // Day 1 label should appear at start
    const dayOnes = screen.getAllByText('1')
    expect(dayOnes.length).toBeGreaterThan(0)
  })

  it('renders week unit labels in weeks mode', () => {
    const weekUnits = [
      { startDate: new Date('2025-01-06'), endDate: new Date('2025-01-12'), days: 7, label: 'Jan 6', hasToday: false, width: 56 },
      { startDate: new Date('2025-01-13'), endDate: new Date('2025-01-19'), days: 7, label: 'Jan 13', hasToday: false, width: 56 },
    ]
    render(
      <GanttTimelineView
        {...defaultProps({
          timeHeaderUnits: { mode: 'weeks', units: weekUnits },
        })}
      />
    )
    expect(screen.getByText('Jan 6')).toBeInTheDocument()
    expect(screen.getByText('Jan 13')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// Zoom controls
// ---------------------------------------------------------------------------

describe('GanttTimelineView — zoom controls', () => {
  it('renders zoom out button', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByRole('button', { name: 'Zoom out' })).toBeInTheDocument()
  })

  it('renders zoom in button', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByRole('button', { name: 'Zoom in' })) .toBeInTheDocument()
  })

  it('disables zoom out button at minimum zoom', () => {
    render(<GanttTimelineView {...defaultProps({ zoomIndex: 0 })} />)
    expect(screen.getByRole('button', { name: 'Zoom out' })).toBeDisabled()
  })

  it('disables zoom in button at maximum zoom', () => {
    render(<GanttTimelineView {...defaultProps({ zoomIndex: ZOOM_LEVELS.length - 1 })} />)
    expect(screen.getByRole('button', { name: 'Zoom in' })).toBeDisabled()
  })

  it('calls setZoomIndex when zoom out is clicked', () => {
    const setZoomIndex = jest.fn()
    render(<GanttTimelineView {...defaultProps({ setZoomIndex, zoomIndex: 3 })} />)
    fireEvent.click(screen.getByRole('button', { name: 'Zoom out' }))
    expect(setZoomIndex).toHaveBeenCalledTimes(1)
  })

  it('calls setZoomIndex when zoom in is clicked', () => {
    const setZoomIndex = jest.fn()
    render(<GanttTimelineView {...defaultProps({ setZoomIndex, zoomIndex: 3 })} />)
    fireEvent.click(screen.getByRole('button', { name: 'Zoom in' }))
    expect(setZoomIndex).toHaveBeenCalledTimes(1)
  })

  it('does not render fullscreen button when showFullscreen is false', () => {
    render(<GanttTimelineView {...defaultProps({ showFullscreen: false })} />)
    expect(screen.queryByRole('button', { name: /fullscreen/i })).not.toBeInTheDocument()
  })

  it('renders fullscreen button when showFullscreen is true', () => {
    render(<GanttTimelineView {...defaultProps({ showFullscreen: true })} />)
    expect(screen.getByRole('button', { name: /fullscreen/i })).toBeInTheDocument()
  })

  it('calls toggleFullscreen when the fullscreen button is clicked', () => {
    const toggleFullscreen = jest.fn()
    render(<GanttTimelineView {...defaultProps({ showFullscreen: true, toggleFullscreen })} />)
    fireEvent.click(screen.getByRole('button', { name: /fullscreen/i }))
    expect(toggleFullscreen).toHaveBeenCalledTimes(1)
  })
})

// ---------------------------------------------------------------------------
// Empty state
// ---------------------------------------------------------------------------

describe('GanttTimelineView — empty state', () => {
  it('renders no info rows when tasks array is empty', () => {
    render(<GanttTimelineView {...defaultProps({ tasks: [] })} />)
    // No gantt-info-row elements means no task title links
    expect(screen.queryAllByRole('button', { name: /expand|collapse/i })).toHaveLength(0)
  })
})

// ---------------------------------------------------------------------------
// Task bars and info rows
// ---------------------------------------------------------------------------

describe('GanttTimelineView — task rows', () => {
  it('renders a row title for each task', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Alpha Task', status: 'in_progress' })),
      makeTask(makeItem({ id: 'b', title: 'Beta Task', status: 'completed' })),
    ]
    // Use width <= 80 so bar label is empty (avoids duplicate title text nodes)
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 80, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, getBarPosition })} />)
    // Titles appear in the info column
    expect(screen.getAllByText('Alpha Task').length).toBeGreaterThanOrEqual(1)
    expect(screen.getAllByText('Beta Task').length).toBeGreaterThanOrEqual(1)
  })

  it('calls handleItemClick when a row title is clicked', () => {
    const handleItemClick = jest.fn()
    const item = makeItem({ id: 'a', title: 'Clickable Task', status: 'not_started' })
    const tasks = [makeTask(item)]
    // Use width <= 80 so the bar label renders empty and title only appears in the info row
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 80, top: 0, height: 20 })
    const { container } = render(<GanttTimelineView {...defaultProps({ tasks, handleItemClick, getBarPosition })} />)
    // Target the info-column title span specifically to avoid the bar label span
    const titleSpan = container.querySelector('.gantt-row-title') as HTMLElement
    expect(titleSpan).toBeInTheDocument()
    fireEvent.click(titleSpan)
    expect(handleItemClick).toHaveBeenCalledWith(item)
  })

  it('renders a collapse button for tasks that have children', () => {
    const tasks = [
      makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, getBarPosition })} />)
    expect(screen.getByRole('button', { name: /collapse/i })).toBeInTheDocument()
  })

  it('shows expand button for a collapsed parent task', () => {
    const tasks = [
      makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(
      <GanttTimelineView
        {...defaultProps({ tasks, collapsed: new Set(['p']), getBarPosition })}
      />
    )
    expect(screen.getByRole('button', { name: 'Expand' })).toBeInTheDocument()
  })

  it('calls toggleCollapse when the collapse button is clicked', () => {
    const toggleCollapse = jest.fn()
    const tasks = [
      makeTask(makeItem({ id: 'p', title: 'Parent', status: 'in_progress' }), { hasChildren: true }),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, toggleCollapse, getBarPosition })} />)
    fireEvent.click(screen.getByRole('button', { name: 'Collapse' }))
    expect(toggleCollapse).toHaveBeenCalledWith('p')
  })

  it('renders category badge when showCategory is true and item has a category', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'product' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, showCategory: true, getBarPosition })} />)
    expect(screen.getByText('product')).toBeInTheDocument()
  })

  it('does not render category badge when showCategory is false', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress', category: 'product' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, showCategory: false, getBarPosition })} />)
    expect(screen.queryByText('product')).not.toBeInTheDocument()
  })

  it('renders status badge when showStatus is true', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, showStatus: true, getBarPosition })} />)
    expect(screen.getByText('in progress')).toBeInTheDocument()
  })

  it('does not render status badge when showStatus is false', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Task A', status: 'in_progress' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    render(<GanttTimelineView {...defaultProps({ tasks, showStatus: false, getBarPosition })} />)
    expect(screen.queryByText('in progress')).not.toBeInTheDocument()
  })

  it('applies gantt-row-focused class to the focused row', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'Alpha', status: 'not_started' })),
      makeTask(makeItem({ id: 'b', title: 'Beta', status: 'not_started' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 200, top: 0, height: 20 })
    const { container } = render(
      <GanttTimelineView {...defaultProps({ tasks, focusedRowIndex: 0, getBarPosition })} />
    )
    const focusedRows = container.querySelectorAll('.gantt-row-focused')
    expect(focusedRows).toHaveLength(1)
  })
})

// ---------------------------------------------------------------------------
// Legend
// ---------------------------------------------------------------------------

describe('GanttTimelineView — legend', () => {
  it('renders the On Track legend entry', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByText('On Track')).toBeInTheDocument()
  })

  it('renders the At Risk legend entry', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByText('At Risk')).toBeInTheDocument()
  })

  it('renders the Blocked legend entry', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByText('Blocked')).toBeInTheDocument()
  })

  it('renders the Not Started legend entry', () => {
    render(<GanttTimelineView {...defaultProps()} />)
    expect(screen.getByText('Not Started')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// Dependency paths
// ---------------------------------------------------------------------------

describe('GanttTimelineView — dependency paths', () => {
  it('does not render an SVG overlay when dependencyPaths is empty', () => {
    const { container } = render(<GanttTimelineView {...defaultProps({ dependencyPaths: [] })} />)
    expect(container.querySelector('.gantt-deps-svg')).not.toBeInTheDocument()
  })

  it('renders an SVG overlay when dependencyPaths has entries', () => {
    const tasks = [
      makeTask(makeItem({ id: 'a', title: 'A', status: 'in_progress' })),
      makeTask(makeItem({ id: 'b', title: 'B', status: 'not_started' })),
    ]
    const getBarPosition = jest.fn().mockReturnValue({ left: 0, width: 100, top: 0, height: 20 })
    const paths = [{ path: 'M 100 10 C 150 10, 150 46, 200 46', fromId: 'a', toId: 'b' }]
    const { container } = render(
      <GanttTimelineView {...defaultProps({ tasks, dependencyPaths: paths, getBarPosition })} />
    )
    expect(container.querySelector('.gantt-deps-svg')).toBeInTheDocument()
  })
})
