import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { StatusBar } from './StatusBar'
import type { VaultOption } from './StatusBar'
import { formatIndexedElapsed } from '../utils/indexingHelpers'

vi.mock('../utils/url', async () => {
  const actual = await vi.importActual('../utils/url')
  return { ...actual, openExternalUrl: vi.fn().mockResolvedValue(undefined) }
})

const { openExternalUrl } = await import('../utils/url') as typeof import('../utils/url') & { openExternalUrl: ReturnType<typeof vi.fn> }

const vaults: VaultOption[] = [
  { label: 'Main Vault', path: '/Users/luca/Laputa' },
  { label: 'Work Vault', path: '/Users/luca/Work' },
]

describe('StatusBar', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('displays note count', () => {
    render(<StatusBar noteCount={9200} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.getByText('9,200 notes')).toBeInTheDocument()
  })

  it('displays build number when provided', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b223" />)
    expect(screen.getByText('b223')).toBeInTheDocument()
  })

  it('displays fallback build number when not provided', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.getByText('b?')).toBeInTheDocument()
  })

  it('calls onCheckForUpdates when clicking build number', () => {
    const onCheckForUpdates = vi.fn()
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b281" onCheckForUpdates={onCheckForUpdates} />)
    fireEvent.click(screen.getByTestId('status-build-number'))
    expect(onCheckForUpdates).toHaveBeenCalledOnce()
  })

  it('build number has "Check for updates" title', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b281" onCheckForUpdates={vi.fn()} />)
    expect(screen.getByTitle('Check for updates')).toBeInTheDocument()
  })

  it('does not display branch name', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.queryByText('main')).not.toBeInTheDocument()
  })

  it('shows clickable commit hash that opens URL via openExternalUrl', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        lastCommitInfo={{ shortHash: 'a3f9b1c', commitUrl: 'https://github.com/owner/repo/commit/abc123' }}
      />
    )
    const link = screen.getByTestId('status-commit-link')
    expect(link).toBeInTheDocument()
    expect(link.tagName).toBe('SPAN')
    expect(screen.getByText('a3f9b1c')).toBeInTheDocument()

    fireEvent.click(link)
    expect(openExternalUrl).toHaveBeenCalledWith('https://github.com/owner/repo/commit/abc123')
  })

  it('shows non-clickable commit hash when no commitUrl', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        lastCommitInfo={{ shortHash: 'b4e2d8f', commitUrl: null }}
      />
    )
    const span = screen.getByTestId('status-commit-hash')
    expect(span).toBeInTheDocument()
    expect(span.tagName).toBe('SPAN')
    expect(screen.getByText('b4e2d8f')).toBeInTheDocument()
  })

  it('hides commit hash when lastCommitInfo is null', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} lastCommitInfo={null} />
    )
    expect(screen.queryByTestId('status-commit-link')).not.toBeInTheDocument()
    expect(screen.queryByTestId('status-commit-hash')).not.toBeInTheDocument()
  })

  it('displays active vault name', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.getByText('Main Vault')).toBeInTheDocument()
  })

  it('shows fallback "Vault" when vault path does not match', () => {
    render(<StatusBar noteCount={100} vaultPath="/unknown/path" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.getByText('Vault')).toBeInTheDocument()
  })

  it('opens vault menu on click and shows all vault options', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)

    // Click the vault button to open menu
    fireEvent.click(screen.getByTitle('Switch vault'))

    expect(screen.getByText('Work Vault')).toBeInTheDocument()
  })

  it('calls onSwitchVault when selecting a different vault', () => {
    const onSwitchVault = vi.fn()
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={onSwitchVault} />)

    fireEvent.click(screen.getByTitle('Switch vault'))
    // Click "Work Vault"
    fireEvent.click(screen.getByText('Work Vault'))

    expect(onSwitchVault).toHaveBeenCalledWith('/Users/luca/Work')
  })

  it('closes vault menu when clicking outside', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)

    fireEvent.click(screen.getByTitle('Switch vault'))
    expect(screen.getByText('Work Vault')).toBeInTheDocument()

    // Click outside the menu
    fireEvent.mouseDown(document.body)

    expect(screen.queryByText('Work Vault')).not.toBeInTheDocument()
  })

  it('toggles vault menu open and closed', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)

    const vaultButton = screen.getByTitle('Switch vault')
    fireEvent.click(vaultButton)
    expect(screen.getByText('Work Vault')).toBeInTheDocument()

    // Click again to close
    fireEvent.click(vaultButton)
    expect(screen.queryByText('Work Vault')).not.toBeInTheDocument()
  })

  it('shows "Open local folder" option in vault menu', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={vi.fn()} />
    )
    fireEvent.click(screen.getByTitle('Switch vault'))
    expect(screen.getByText('Open local folder')).toBeInTheDocument()
  })

  it('calls onOpenLocalFolder when clicking "Open local folder"', () => {
    const onOpenLocalFolder = vi.fn()
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={onOpenLocalFolder} />
    )
    fireEvent.click(screen.getByTitle('Switch vault'))
    fireEvent.click(screen.getByText('Open local folder'))
    expect(onOpenLocalFolder).toHaveBeenCalledOnce()
  })

  it('shows add-vault options in vault menu', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        onOpenLocalFolder={vi.fn()}
        onConnectGitHub={vi.fn()}
      />
    )
    fireEvent.click(screen.getByTitle('Switch vault'))
    expect(screen.getByText('Open local folder')).toBeInTheDocument()
    expect(screen.getByText('Connect GitHub repo')).toBeInTheDocument()
  })

  it('shows modified count when modifiedCount is > 0', () => {
    render(<StatusBar noteCount={100} modifiedCount={3} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.getByTestId('status-modified-count')).toBeInTheDocument()
    expect(screen.getByText('3 pending')).toBeInTheDocument()
  })

  it('does not show modified count when modifiedCount is 0', () => {
    render(<StatusBar noteCount={100} modifiedCount={0} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument()
  })

  it('does not show modified count when modifiedCount is not provided', () => {
    render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
    expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument()
  })

  it('closes menu after clicking "Open local folder"', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={vi.fn()} />
    )
    fireEvent.click(screen.getByTitle('Switch vault'))
    fireEvent.click(screen.getByText('Open local folder'))
    // Menu should close after clicking an action
    expect(screen.queryByText('Open local folder')).not.toBeInTheDocument()
  })

  it('calls onClickPending when clicking the pending count', () => {
    const onClickPending = vi.fn()
    render(
      <StatusBar noteCount={100} modifiedCount={5} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onClickPending={onClickPending} />
    )
    fireEvent.click(screen.getByTestId('status-modified-count'))
    expect(onClickPending).toHaveBeenCalledOnce()
  })

  it('pending count has title for accessibility', () => {
    render(
      <StatusBar noteCount={100} modifiedCount={3} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onClickPending={vi.fn()} />
    )
    expect(screen.getByTitle('View pending changes')).toBeInTheDocument()
  })

  it('shows indexing badge when indexing is in progress', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'scanning', current: 342, total: 1057, done: false, error: null }}
      />
    )
    expect(screen.getByTestId('status-indexing')).toBeInTheDocument()
    expect(screen.getByText(/Indexing… 342\/1,057/)).toBeInTheDocument()
  })

  it('shows embedding phase in indexing badge', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'embedding', current: 50, total: 200, done: false, error: null }}
      />
    )
    expect(screen.getByText(/Embedding… 50\/200/)).toBeInTheDocument()
  })

  it('shows index ready when indexing is complete', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'complete', current: 1057, total: 1057, done: true, error: null }}
      />
    )
    expect(screen.getByText('Index ready')).toBeInTheDocument()
  })

  it('shows error state in indexing badge with retry label', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'error', current: 0, total: 0, done: true, error: 'qmd update failed' }}
      />
    )
    expect(screen.getByText('Index failed — retry')).toBeInTheDocument()
  })

  it('hides indexing badge when phase is unavailable', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'unavailable', current: 0, total: 0, done: true, error: 'qmd not available' }}
      />
    )
    expect(screen.queryByTestId('status-indexing')).not.toBeInTheDocument()
  })

  it('calls onRetryIndexing when clicking error badge', () => {
    const onRetryIndexing = vi.fn()
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'error', current: 0, total: 0, done: true, error: 'qmd update failed' }}
        onRetryIndexing={onRetryIndexing}
      />
    )
    fireEvent.click(screen.getByTestId('status-indexing'))
    expect(onRetryIndexing).toHaveBeenCalledOnce()
  })

  it('hides indexing badge when phase is idle', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'idle', current: 0, total: 0, done: false, error: null }}
      />
    )
    expect(screen.queryByTestId('status-indexing')).not.toBeInTheDocument()
  })

  it('hides indexing badge when no progress prop provided', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />
    )
    expect(screen.queryByTestId('status-indexing')).not.toBeInTheDocument()
  })

  it('shows installing phase in indexing badge', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'installing', current: 0, total: 0, done: false, error: null }}
      />
    )
    expect(screen.getByText('Installing search…')).toBeInTheDocument()
  })

  it('shows MCP warning badge when status is not_installed', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="not_installed" />
    )
    expect(screen.getByTestId('status-mcp')).toBeInTheDocument()
    expect(screen.getByTitle('MCP server not installed — click to install')).toBeInTheDocument()
  })

  it('shows MCP warning badge when status is no_claude_cli', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="no_claude_cli" />
    )
    expect(screen.getByTestId('status-mcp')).toBeInTheDocument()
    expect(screen.getByTitle('Claude CLI not found — install it first')).toBeInTheDocument()
  })

  it('hides MCP badge when status is installed', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="installed" />
    )
    expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
  })

  it('hides MCP badge when status is checking', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="checking" />
    )
    expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
  })

  it('hides MCP badge when no mcpStatus prop provided', () => {
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />
    )
    expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
  })

  it('calls onInstallMcp when clicking MCP badge with not_installed status', () => {
    const onInstallMcp = vi.fn()
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="not_installed" onInstallMcp={onInstallMcp} />
    )
    fireEvent.click(screen.getByTestId('status-mcp'))
    expect(onInstallMcp).toHaveBeenCalledOnce()
  })

  it('does not call onInstallMcp when clicking MCP badge with no_claude_cli status', () => {
    const onInstallMcp = vi.fn()
    render(
      <StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="no_claude_cli" onInstallMcp={onInstallMcp} />
    )
    fireEvent.click(screen.getByTestId('status-mcp'))
    expect(onInstallMcp).not.toHaveBeenCalled()
  })

  it('shows "Indexed just now" when lastIndexedTime is recent and phase is idle', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'idle', current: 0, total: 0, done: false, error: null }}
        lastIndexedTime={Date.now() - 5000}
      />
    )
    expect(screen.getByText(/Indexed just now/)).toBeInTheDocument()
    expect(screen.getByTestId('status-indexed-time')).toBeInTheDocument()
  })

  it('calls onReindexVault when clicking the indexed time badge', () => {
    const onReindexVault = vi.fn()
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'idle', current: 0, total: 0, done: false, error: null }}
        lastIndexedTime={Date.now() - 5000}
        onReindexVault={onReindexVault}
      />
    )
    fireEvent.click(screen.getByTestId('status-indexed-time'))
    expect(onReindexVault).toHaveBeenCalledOnce()
  })

  it('hides indexed time badge when no lastIndexedTime', () => {
    render(
      <StatusBar
        noteCount={100}
        vaultPath="/Users/luca/Laputa"
        vaults={vaults}
        onSwitchVault={vi.fn()}
        indexingProgress={{ phase: 'idle', current: 0, total: 0, done: false, error: null }}
      />
    )
    expect(screen.queryByTestId('status-indexed-time')).not.toBeInTheDocument()
  })
})

describe('formatIndexedElapsed', () => {
  it('returns empty string for null', () => {
    expect(formatIndexedElapsed(null)).toBe('')
  })

  it('returns "Indexed just now" for < 60s', () => {
    expect(formatIndexedElapsed(Date.now() - 30_000)).toBe('Indexed just now')
  })

  it('returns minutes for < 60min', () => {
    expect(formatIndexedElapsed(Date.now() - 5 * 60_000)).toBe('Indexed 5m ago')
  })

  it('returns hours for < 24h', () => {
    expect(formatIndexedElapsed(Date.now() - 3 * 3600_000)).toBe('Indexed 3h ago')
  })

  it('returns days for >= 24h', () => {
    expect(formatIndexedElapsed(Date.now() - 48 * 3600_000)).toBe('Indexed 2d ago')
  })
})
