import { describe, it, expect, vi, beforeEach } from 'vitest'
import { act } from 'react'
import { render, fireEvent } from '../../../test/utils'
import { TimelinePreview } from '../TimelinePreview'
import type { PausePointData, VideoSegmentMetadata } from '../../../types/api'

// ── helpers ──────────────────────────────────────────────────────────────────

function makePausePoint(overrides: Partial<PausePointData> = {}): PausePointData {
  return {
    cue_index: 0,
    original_ms: 5000,
    source_ms: null,
    adjusted_ms: null,
    min_bound_ms: 1000,
    max_bound_ms: 9000,
    natural_gap_ms: 0,
    ...overrides,
  }
}

function makeSegment(overrides: Partial<VideoSegmentMetadata> = {}): VideoSegmentMetadata {
  return {
    segment_index: 0,
    start_ms: 0,
    end_ms: 5000,
    gcs_uri: 'gs://test/segment.mp4',
    duration_ms: 5000,
    is_freeze_frame: false,
    cue_index: null,
    ...overrides,
  }
}

function makeFreezeSegment(cueIndex: number, startMs: number): VideoSegmentMetadata {
  return makeSegment({
    segment_index: 1,
    is_freeze_frame: true,
    cue_index: cueIndex,
    start_ms: startMs,
    end_ms: startMs + 6000,
    duration_ms: 6000,
  })
}

// Default props used in most tests
function defaultProps(overrides: Record<string, unknown> = {}) {
  return {
    segments: [],
    pausePoints: [],
    totalDurationMs: 10000,
    currentTimeMs: 0,
    onSegmentClick: vi.fn(),
    onPausePointClick: vi.fn(),
    onPausePointUpdate: vi.fn(),
    onRegenerateTTS: vi.fn(),
    regenerationQueue: [],
    ...overrides,
  }
}

// Mock pointer capture (jsdom does not implement setPointerCapture)
beforeEach(() => {
  Element.prototype.setPointerCapture = vi.fn()
  Element.prototype.releasePointerCapture = vi.fn()
})

// jsdom's PointerEvent doesn't expose MouseEvent properties (button, clientX) from init —
// they come back as undefined, breaking `if (e.button !== 0)` and delta math.
// Work-around: dispatch MouseEvents typed as pointer events. MouseEvent correctly handles
// all MouseEventInit fields; React 19 routes by event.type, not constructor type.
function ptrDown(el: HTMLElement, init: { button?: number; clientX: number }) {
  fireEvent(el, new MouseEvent('pointerdown', {
    bubbles: true, cancelable: true,
    button: init.button ?? 0, clientX: init.clientX,
  }))
}

function ptrMove(el: HTMLElement, init: { clientX: number }) {
  fireEvent(el, new MouseEvent('pointermove', {
    bubbles: true, cancelable: true, clientX: init.clientX,
  }))
}

function ptrUp(el: HTMLElement, init: { clientX: number }) {
  fireEvent(el, new MouseEvent('pointerup', {
    bubbles: true, cancelable: true, clientX: init.clientX,
  }))
}

// Helper to mock the timeline container rect so clientX→ms math works
function mockTimelineRect(container: HTMLElement) {
  const timelineDiv = container.querySelector('.relative.h-16') as HTMLElement
  if (timelineDiv) {
    vi.spyOn(timelineDiv, 'getBoundingClientRect').mockReturnValue({
      left: 0, right: 1000, width: 1000, top: 0, bottom: 64, height: 64, x: 0, y: 0,
      toJSON: () => {},
    } as DOMRect)
  }
  return timelineDiv
}

// ── Marker drag tests (B1) ────────────────────────────────────────────────────

describe('TimelinePreview — marker drag', () => {
  it('calls onPausePointUpdate when marker is dragged and released', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement
    expect(marker).toBeTruthy()

    // Drag: down at x=500 (5s), move to x=700 (7s), up at x=700
    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 }) // >3px threshold
      ptrMove(marker, { clientX: 700 })
      ptrUp(marker, { clientX: 700 })
    })

    expect(onPausePointUpdate).toHaveBeenCalledOnce()
    const [cueIndex, adjustedMs] = onPausePointUpdate.mock.calls[0]
    expect(cueIndex).toBe(0)
    // 700/1000 * 10000 = 7000ms, within bounds [1000, 9000]
    expect(adjustedMs).toBe(7000)
  })

  it('opens editor popover on click (no movement)', async () => {
    const onPausePointUpdate = vi.fn()
    const onPausePointClick = vi.fn()
    const pp = makePausePoint({ cue_index: 0 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate, onPausePointClick })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      // No move — stayed at same position
      ptrUp(marker, { clientX: 500 })
    })

    expect(onPausePointUpdate).not.toHaveBeenCalled()
    expect(onPausePointClick).toHaveBeenCalledWith(pp)
  })

  it('does NOT call onPausePointUpdate if final position equals original', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    // Drag to 500 → 504 → 500 (same ms as start = 5000ms)
    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 500 })
      ptrUp(marker, { clientX: 500 })
    })

    expect(onPausePointUpdate).not.toHaveBeenCalled()
  })

  it('clamps drag to min_bound_ms', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000, min_bound_ms: 2000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    // Drag to x=50 → 500ms, below min_bound_ms=2000ms
    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 50 })
      ptrUp(marker, { clientX: 50 })
    })

    const [, adjustedMs] = onPausePointUpdate.mock.calls[0]
    expect(adjustedMs).toBe(2000) // clamped to min_bound_ms
  })

  it('clamps drag to max_bound_ms', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000, max_bound_ms: 8000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    // Drag to x=950 → 9500ms, above max_bound_ms=8000ms
    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 950 })
      ptrUp(marker, { clientX: 950 })
    })

    const [, adjustedMs] = onPausePointUpdate.mock.calls[0]
    expect(adjustedMs).toBe(8000) // clamped to max_bound_ms
  })

  it('right-click does NOT start drag (context menu allowed)', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    await act(async () => {
      ptrDown(marker, { button: 2, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 700 })
      ptrUp(marker, { clientX: 700 })
    })

    expect(onPausePointUpdate).not.toHaveBeenCalled()
  })
})

// ── Freeze-block drag tests (B2) ─────────────────────────────────────────────

describe('TimelinePreview — freeze-block drag', () => {
  it('calls onPausePointUpdate when freeze block is dragged', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000 })
    const freeze = makeFreezeSegment(0, 5000)
    const { container } = render(
      <TimelinePreview
        {...defaultProps({
          pausePoints: [pp],
          segments: [freeze],
          onPausePointUpdate,
        })}
      />
    )

    mockTimelineRect(container)
    const block = container.querySelector('[title*="AD Cue 1"]') as HTMLElement
    expect(block).toBeTruthy()

    // Drag block from x=500 → x=600 (+1s = 1000ms delta)
    await act(async () => {
      ptrDown(block, { button: 0, clientX: 500 })
      ptrMove(block, { clientX: 504 })
      ptrMove(block, { clientX: 600 })
      ptrUp(block, { clientX: 600 })
    })

    expect(onPausePointUpdate).toHaveBeenCalledOnce()
    const [cueIndex, adjustedMs] = onPausePointUpdate.mock.calls[0]
    expect(cueIndex).toBe(0)
    expect(adjustedMs).toBe(6000) // 600/1000 * 10000 = 6000ms
  })

  it('non-freeze segments do not start drag', async () => {
    const onPausePointUpdate = vi.fn()
    const seg = makeSegment({ segment_index: 0, is_freeze_frame: false, start_ms: 0, duration_ms: 4000 })
    const { container } = render(
      <TimelinePreview
        {...defaultProps({ segments: [seg], onPausePointUpdate })}
      />
    )

    mockTimelineRect(container)
    const videoSeg = container.querySelector('[title*="Video segment"]') as HTMLElement
    if (!videoSeg) return // no drag handlers on non-freeze segments → trivially passes

    await act(async () => {
      ptrDown(videoSeg, { button: 0, clientX: 200 })
      ptrMove(videoSeg, { clientX: 204 })
      ptrMove(videoSeg, { clientX: 400 })
      ptrUp(videoSeg, { clientX: 400 })
    })

    expect(onPausePointUpdate).not.toHaveBeenCalled()
  })
})

// ── Drag tooltip (B3) ─────────────────────────────────────────────────────────

describe('TimelinePreview — drag tooltip', () => {
  it('shows time tooltip during marker drag', async () => {
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp] })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    // Wrap in act so React flushes state updates before we query the DOM
    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 700 })
    })

    const tooltip = container.querySelector('.bg-gray-800') as HTMLElement
    expect(tooltip).toBeTruthy()
    expect(tooltip.textContent).toMatch(/\d:\d{2}/)
  })

  it('hides tooltip after drag ends', async () => {
    const onPausePointUpdate = vi.fn()
    const pp = makePausePoint({ cue_index: 0, original_ms: 5000 })
    const { container } = render(
      <TimelinePreview {...defaultProps({ pausePoints: [pp], onPausePointUpdate })} />
    )

    mockTimelineRect(container)
    const marker = container.querySelector('[title*="Pause point 1"]') as HTMLElement

    await act(async () => {
      ptrDown(marker, { button: 0, clientX: 500 })
      ptrMove(marker, { clientX: 504 })
      ptrMove(marker, { clientX: 700 })
      ptrUp(marker, { clientX: 700 })
    })

    const tooltip = container.querySelector('.bg-gray-800')
    expect(tooltip).toBeNull()
  })
})
