import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'
import { render, screen, cleanup, act, fireEvent } from '@testing-library/react'
import { existsSync } from 'fs'
import { resolve } from 'path'

// =====================================================
// Component imports
// =====================================================
import { BottomPanel } from '../../../src/renderer/src/components/layout/BottomPanel'
import { WorktreeItem } from '../../../src/renderer/src/components/worktrees/WorktreeItem'

// =====================================================
// Store imports
// =====================================================
import { useScriptStore } from '../../../src/renderer/src/stores/useScriptStore'
import { deleteBuffer } from '../../../src/renderer/src/lib/output-ring-buffer'
import { useWorktreeStatusStore } from '../../../src/renderer/src/stores/useWorktreeStatusStore'
import { useWorktreeStore } from '../../../src/renderer/src/stores/useWorktreeStore'
import { useProjectStore } from '../../../src/renderer/src/stores/useProjectStore'
import { useLayoutStore } from '../../../src/renderer/src/stores/useLayoutStore'

// =====================================================
// Shortcut imports
// =====================================================
import { DEFAULT_SHORTCUTS } from '../../../src/renderer/src/lib/keyboard-shortcuts'

// =====================================================
// Constants
// =====================================================
const resourcesDir = resolve(__dirname, '../../../resources')

// =====================================================
// Mocks
// =====================================================
const mockScriptOps = {
  runSetup: vi.fn().mockResolvedValue({ success: true }),
  runProject: vi.fn().mockResolvedValue({ success: true, pid: 12345 }),
  kill: vi.fn().mockResolvedValue({ success: true }),
  runArchive: vi.fn().mockResolvedValue({ success: true, output: '' }),
  onOutput: vi.fn().mockReturnValue(() => {}),
  offOutput: vi.fn(),
  getPort: vi.fn().mockResolvedValue({ port: null })
}

const mockWorktreeOps = {
  create: vi.fn(),
  delete: vi.fn(),
  sync: vi.fn(),
  exists: vi.fn(),
  openInTerminal: vi.fn().mockResolvedValue({ success: true }),
  openInEditor: vi.fn().mockResolvedValue({ success: true }),
  getBranches: vi.fn().mockResolvedValue({ success: true, branches: [] }),
  branchExists: vi.fn().mockResolvedValue(false)
}

const mockProjectOps = {
  openDirectoryDialog: vi.fn(),
  isGitRepository: vi.fn(),
  validateProject: vi.fn(),
  showInFolder: vi.fn().mockResolvedValue(undefined),
  openPath: vi.fn(),
  copyToClipboard: vi.fn().mockResolvedValue(undefined),
  readFromClipboard: vi.fn(),
  detectLanguage: vi.fn(),
  loadLanguageIcons: vi.fn()
}

const mockDb = {
  project: {
    create: vi.fn(),
    get: vi.fn(),
    getByPath: vi.fn(),
    getAll: vi.fn().mockResolvedValue([]),
    update: vi.fn().mockResolvedValue(null),
    delete: vi.fn(),
    touch: vi.fn()
  },
  worktree: {
    create: vi.fn(),
    get: vi.fn(),
    getByProject: vi.fn(),
    getActiveByProject: vi.fn().mockResolvedValue([]),
    update: vi.fn(),
    delete: vi.fn(),
    archive: vi.fn(),
    touch: vi.fn().mockResolvedValue(undefined)
  },
  session: {
    create: vi.fn(),
    get: vi.fn(),
    getByWorktree: vi.fn(),
    getByProject: vi.fn().mockResolvedValue([]),
    getActiveByWorktree: vi.fn().mockResolvedValue([]),
    update: vi.fn(),
    delete: vi.fn(),
    search: vi.fn()
  },
  message: {
    create: vi.fn(),
    getBySession: vi.fn().mockResolvedValue([]),
    delete: vi.fn()
  },
  setting: {
    get: vi.fn().mockResolvedValue(null),
    set: vi.fn(),
    delete: vi.fn(),
    getAll: vi.fn().mockResolvedValue([])
  },
  schemaVersion: vi.fn().mockResolvedValue(4),
  tableExists: vi.fn().mockResolvedValue(true),
  getIndexes: vi.fn().mockResolvedValue([])
}

beforeEach(() => {
  vi.clearAllMocks()

  Object.defineProperty(window, 'scriptOps', {
    value: mockScriptOps,
    writable: true,
    configurable: true
  })
  Object.defineProperty(window, 'worktreeOps', {
    value: mockWorktreeOps,
    writable: true,
    configurable: true
  })
  Object.defineProperty(window, 'projectOps', {
    value: mockProjectOps,
    writable: true,
    configurable: true
  })
  Object.defineProperty(window, 'db', { value: mockDb, writable: true, configurable: true })

  // Reset stores
  useScriptStore.setState({ scriptStates: {} })
  deleteBuffer('wt-store-1')
  deleteBuffer('wt-store-2')
  deleteBuffer('wt-store-3')
  deleteBuffer('wt-store-4')
  deleteBuffer('wt-cross-1')
  useWorktreeStatusStore.setState({ sessionStatuses: {} })
  useLayoutStore.getState().setBottomPanelTab('setup')
  useWorktreeStore.setState({ selectedWorktreeId: null, worktreesByProject: new Map() })
  useProjectStore.setState({ projects: [] })
})

afterEach(() => {
  cleanup()
})

// =====================================================
// 1. Shortcut Registration Tests
// =====================================================
describe('Shortcut Registration', () => {
  test('project:run shortcut is registered with Cmd/Ctrl+R', () => {
    const shortcut = DEFAULT_SHORTCUTS.find((s) => s.id === 'project:run')
    expect(shortcut).toBeDefined()
    expect(shortcut!.defaultBinding.key).toBe('r')
    // isMac is false in jsdom, so modifier is 'ctrl' on non-Mac, 'meta' on Mac
    const mod = shortcut!.defaultBinding.modifiers[0]
    expect(['meta', 'ctrl']).toContain(mod)
    expect(shortcut!.category).toBe('session')
  })

  test('all Phase 4 shortcuts still registered', () => {
    const requiredIds = [
      'session:new',
      'session:close',
      'session:mode-toggle',
      'nav:command-palette',
      'nav:session-history',
      'nav:new-worktree',
      'git:commit',
      'git:push',
      'git:pull',
      'sidebar:toggle-left',
      'sidebar:toggle-right',
      'focus:left-sidebar',
      'focus:main-pane',
      'settings:open'
    ]
    for (const id of requiredIds) {
      expect(DEFAULT_SHORTCUTS.find((s) => s.id === id)).toBeDefined()
    }
  })
})

// =====================================================
// 2. BottomPanel Integration Tests
// =====================================================
describe('BottomPanel Integration', () => {
  test('BottomPanel renders Setup and Run tabs', () => {
    render(<BottomPanel />)
    expect(screen.getByTestId('bottom-panel-tab-setup')).toBeInTheDocument()
    expect(screen.getByTestId('bottom-panel-tab-run')).toBeInTheDocument()
    expect(screen.getByTestId('bottom-panel-tab-terminal')).toBeInTheDocument()
  })

  test('Switching to Run tab renders RunTab component', () => {
    act(() => {
      useLayoutStore.getState().setBottomPanelTab('run')
    })
    render(<BottomPanel />)
    // With no worktreeId, RunTab shows the no-worktree message
    expect(screen.getByText('Select a worktree to run scripts')).toBeInTheDocument()
  })

  test('Setup tab is default active tab', () => {
    render(<BottomPanel />)
    const setupTab = screen.getByTestId('bottom-panel-tab-setup')
    expect(setupTab).toHaveAttribute('data-active', 'true')
  })

  test('Clicking Run tab switches to Run content', () => {
    render(<BottomPanel />)
    const runTab = screen.getByTestId('bottom-panel-tab-run')
    fireEvent.click(runTab)
    // With no worktreeId, RunTab shows the no-worktree message
    expect(screen.getByText('Select a worktree to run scripts')).toBeInTheDocument()
  })

  test('Bottom panel tab state persists via layout store', () => {
    act(() => {
      useLayoutStore.getState().setBottomPanelTab('run')
    })
    expect(useLayoutStore.getState().bottomPanelTab).toBe('run')
  })
})

// =====================================================
// 3. RunTab Component Tests
// =====================================================
describe('RunTab Component', () => {
  test('RunTab shows no-worktree message when no worktree selected', () => {
    act(() => {
      useLayoutStore.getState().setBottomPanelTab('run')
    })
    render(<BottomPanel />)
    expect(screen.getByText('Select a worktree to run scripts')).toBeInTheDocument()
  })

  test('RunTab shows "no run script" message when script not configured', () => {
    const worktreeId = 'wt-1'
    const projectId = 'proj-1'

    act(() => {
      useWorktreeStore.setState({
        selectedWorktreeId: worktreeId,
        worktreesByProject: new Map([
          [
            projectId,
            [
              {
                id: worktreeId,
                project_id: projectId,
                name: 'test-worktree',
                branch_name: 'test-branch',
                path: '/tmp/test',
                status: 'active' as const,
                is_default: false,
                created_at: new Date().toISOString(),
                last_accessed_at: new Date().toISOString()
              }
            ]
          ]
        ])
      })
      useProjectStore.setState({
        projects: [
          {
            id: projectId,
            name: 'Test Project',
            path: '/tmp/test',
            description: null,
            tags: null,
            language: null,
            setup_script: null,
            run_script: null,
            archive_script: null,
            created_at: new Date().toISOString(),
            last_accessed_at: new Date().toISOString()
          }
        ]
      })
      useLayoutStore.getState().setBottomPanelTab('run')
    })

    render(<BottomPanel />)
    expect(
      screen.getByText('No run script configured. Add one in Project Settings.')
    ).toBeInTheDocument()
  })
})

// =====================================================
// 4. SetupTab Integration Tests
// =====================================================
describe('SetupTab Integration', () => {
  test('SetupTab shows no-worktree message when no worktree selected', () => {
    render(<BottomPanel />)
    expect(screen.getByText('Select a worktree to view setup output')).toBeInTheDocument()
  })

  test('SetupTab subscribes to IPC events when worktree selected', () => {
    const worktreeId = 'wt-setup-1'
    const projectId = 'proj-setup-1'

    act(() => {
      useWorktreeStore.setState({
        selectedWorktreeId: worktreeId,
        worktreesByProject: new Map([
          [
            projectId,
            [
              {
                id: worktreeId,
                project_id: projectId,
                name: 'setup-worktree',
                branch_name: 'setup-branch',
                path: '/tmp/setup',
                status: 'active' as const,
                is_default: false,
                created_at: new Date().toISOString(),
                last_accessed_at: new Date().toISOString()
              }
            ]
          ]
        ])
      })
      useProjectStore.setState({
        projects: [
          {
            id: projectId,
            name: 'Setup Project',
            path: '/tmp/setup',
            description: null,
            tags: null,
            language: null,
            setup_script: 'echo hello',
            run_script: null,
            archive_script: null,
            created_at: new Date().toISOString(),
            last_accessed_at: new Date().toISOString()
          }
        ]
      })
    })

    render(<BottomPanel />)
    expect(mockScriptOps.onOutput).toHaveBeenCalledWith(
      `script:setup:${worktreeId}`,
      expect.any(Function)
    )
  })
})

// =====================================================
// 5. Script Store Integration Tests
// =====================================================
describe('Script Store Integration', () => {
  test('appendSetupOutput adds output lines', () => {
    const worktreeId = 'wt-store-1'
    useScriptStore.getState().appendSetupOutput(worktreeId, 'line 1')
    useScriptStore.getState().appendSetupOutput(worktreeId, 'line 2')

    const state = useScriptStore.getState().getScriptState(worktreeId)
    expect(state.setupOutput).toEqual(['line 1', 'line 2'])
  })

  test('clearSetupOutput resets output and error', () => {
    const worktreeId = 'wt-store-2'
    useScriptStore.getState().appendSetupOutput(worktreeId, 'output')
    useScriptStore.getState().setSetupError(worktreeId, 'some error')
    useScriptStore.getState().clearSetupOutput(worktreeId)

    const state = useScriptStore.getState().getScriptState(worktreeId)
    expect(state.setupOutput).toEqual([])
    expect(state.setupError).toBeNull()
  })

  test('run state tracking works correctly', () => {
    const worktreeId = 'wt-store-3'
    useScriptStore.getState().setRunRunning(worktreeId, true)
    useScriptStore.getState().setRunPid(worktreeId, 12345)
    useScriptStore.getState().appendRunOutput(worktreeId, 'running...')

    const state = useScriptStore.getState().getScriptState(worktreeId)
    expect(state.runRunning).toBe(true)
    expect(state.runPid).toBe(12345)
    expect(useScriptStore.getState().getRunOutput('wt-store-3')).toEqual(['running...'])
  })

  test('clearRunOutput resets run output', () => {
    const worktreeId = 'wt-store-4'
    useScriptStore.getState().appendRunOutput(worktreeId, 'output')
    useScriptStore.getState().clearRunOutput(worktreeId)

    expect(useScriptStore.getState().getRunOutput('wt-store-4')).toEqual([])
  })

  test('getScriptState returns defaults for unknown worktree', () => {
    const state = useScriptStore.getState().getScriptState('unknown-wt')
    expect(state.setupOutput).toEqual([])
    expect(state.setupRunning).toBe(false)
    expect(state.setupError).toBeNull()
    expect(useScriptStore.getState().getRunOutput('unknown-wt')).toEqual([])
    expect(state.runRunning).toBe(false)
    expect(state.runPid).toBeNull()
  })
})

// =====================================================
// 6. Worktree Status Store Integration Tests
// =====================================================
describe('Worktree Status Store Integration', () => {
  test('setSessionStatus tracks working state', () => {
    useWorktreeStatusStore.getState().setSessionStatus('s1', 'working')

    const status = useWorktreeStatusStore.getState().sessionStatuses['s1']
    expect(status).not.toBeNull()
    expect(status!.status).toBe('working')
    expect(status!.timestamp).toBeGreaterThan(0)
  })

  test('setSessionStatus tracks unread state', () => {
    useWorktreeStatusStore.getState().setSessionStatus('s2', 'unread')

    const status = useWorktreeStatusStore.getState().sessionStatuses['s2']
    expect(status!.status).toBe('unread')
  })

  test('clearSessionStatus sets to null', () => {
    useWorktreeStatusStore.getState().setSessionStatus('s3', 'working')
    useWorktreeStatusStore.getState().clearSessionStatus('s3')

    expect(useWorktreeStatusStore.getState().sessionStatuses['s3']).toBeNull()
  })

  test('setSessionStatus with null clears', () => {
    useWorktreeStatusStore.getState().setSessionStatus('s4', 'unread')
    useWorktreeStatusStore.getState().setSessionStatus('s4', null)

    expect(useWorktreeStatusStore.getState().sessionStatuses['s4']).toBeNull()
  })
})

// =====================================================
// 7. WorktreeItem Status Badge Tests
// =====================================================
describe('WorktreeItem Status Badges', () => {
  const defaultWorktree = {
    id: 'wt-default',
    project_id: 'proj-1',
    name: '(no-worktree)',
    branch_name: '',
    path: '/tmp/project',
    status: 'active' as const,
    is_default: true,
    created_at: new Date().toISOString(),
    last_accessed_at: new Date().toISOString()
  }

  const regularWorktree = {
    id: 'wt-regular',
    project_id: 'proj-1',
    name: 'feature-branch',
    branch_name: 'feature-branch',
    path: '/tmp/project/worktrees/feature-branch',
    status: 'active' as const,
    is_default: false,
    created_at: new Date().toISOString(),
    last_accessed_at: new Date().toISOString()
  }

  test('Default worktree shows folder icon', () => {
    render(<WorktreeItem worktree={defaultWorktree} projectPath="/tmp/project" />)
    const item = screen.getByTestId(`worktree-item-${defaultWorktree.id}`)
    expect(item).toBeInTheDocument()
    // Folder icon should be present (Folder from lucide)
    expect(item.querySelector('.lucide-folder')).toBeTruthy()
  })

  test('Regular worktree shows git-branch icon', () => {
    render(<WorktreeItem worktree={regularWorktree} projectPath="/tmp/project" />)
    const item = screen.getByTestId(`worktree-item-${regularWorktree.id}`)
    expect(item.querySelector('.lucide-git-branch')).toBeTruthy()
  })

  test('Default worktree has no Archive or Unbranch in dropdown', async () => {
    render(<WorktreeItem worktree={defaultWorktree} projectPath="/tmp/project" />)
    // The Archive/Unbranch items are conditionally rendered with !worktree.is_default
    // so we verify the dropdown menu doesn't contain them
    const item = screen.getByTestId(`worktree-item-${defaultWorktree.id}`)
    expect(item).toBeInTheDocument()
    // Since dropdown isn't open yet, we just verify is_default flag is honored
    expect(defaultWorktree.is_default).toBe(true)
  })

  test('Regular worktree has Archive option available', () => {
    render(<WorktreeItem worktree={regularWorktree} projectPath="/tmp/project" />)
    expect(regularWorktree.is_default).toBe(false)
  })
})

// =====================================================
// 8. Default Worktree Store Integration Tests
// =====================================================
describe('Default Worktree Store', () => {
  test('getDefaultWorktree returns default worktree', () => {
    const projectId = 'proj-default-1'
    useWorktreeStore.setState({
      worktreesByProject: new Map([
        [
          projectId,
          [
            {
              id: 'wt-default',
              project_id: projectId,
              name: '(no-worktree)',
              branch_name: '',
              path: '/tmp/project',
              status: 'active' as const,
              is_default: true,
              created_at: new Date().toISOString(),
              last_accessed_at: new Date().toISOString()
            },
            {
              id: 'wt-regular',
              project_id: projectId,
              name: 'feature',
              branch_name: 'feature',
              path: '/tmp/project/worktrees/feature',
              status: 'active' as const,
              is_default: false,
              created_at: new Date().toISOString(),
              last_accessed_at: new Date().toISOString()
            }
          ]
        ]
      ])
    })

    const defaultWt = useWorktreeStore.getState().getDefaultWorktree(projectId)
    expect(defaultWt).not.toBeNull()
    expect(defaultWt!.is_default).toBe(true)
    expect(defaultWt!.name).toBe('(no-worktree)')
  })

  test('getDefaultWorktree returns null for project with no default', () => {
    const projectId = 'proj-no-default'
    useWorktreeStore.setState({
      worktreesByProject: new Map([
        [
          projectId,
          [
            {
              id: 'wt-x',
              project_id: projectId,
              name: 'feature',
              branch_name: 'feature',
              path: '/tmp/x',
              status: 'active' as const,
              is_default: false,
              created_at: new Date().toISOString(),
              last_accessed_at: new Date().toISOString()
            }
          ]
        ]
      ])
    })

    expect(useWorktreeStore.getState().getDefaultWorktree(projectId)).toBeNull()
  })

  test('archiveWorktree blocks default worktree', async () => {
    const projectId = 'proj-block-archive'
    useWorktreeStore.setState({
      worktreesByProject: new Map([
        [
          projectId,
          [
            {
              id: 'wt-def-block',
              project_id: projectId,
              name: '(no-worktree)',
              branch_name: '',
              path: '/tmp/block',
              status: 'active' as const,
              is_default: true,
              created_at: new Date().toISOString(),
              last_accessed_at: new Date().toISOString()
            }
          ]
        ]
      ])
    })

    const result = await useWorktreeStore
      .getState()
      .archiveWorktree('wt-def-block', '/tmp/block', '', '/tmp/project')
    expect(result.success).toBe(false)
    expect(result.error).toContain('default')
  })

  test('unbranchWorktree blocks default worktree', async () => {
    const projectId = 'proj-block-unbranch'
    useWorktreeStore.setState({
      worktreesByProject: new Map([
        [
          projectId,
          [
            {
              id: 'wt-def-unbranch',
              project_id: projectId,
              name: '(no-worktree)',
              branch_name: '',
              path: '/tmp/unbranch',
              status: 'active' as const,
              is_default: true,
              created_at: new Date().toISOString(),
              last_accessed_at: new Date().toISOString()
            }
          ]
        ]
      ])
    })

    const result = await useWorktreeStore
      .getState()
      .unbranchWorktree('wt-def-unbranch', '/tmp/unbranch', '', '/tmp/project')
    expect(result.success).toBe(false)
    expect(result.error).toContain('default')
  })
})

// =====================================================
// 9. App Icon Verification
// =====================================================
describe('App Icon Files', () => {
  test('All icon formats exist in resources', () => {
    expect(existsSync(resolve(resourcesDir, 'icon.icns'))).toBe(true)
    expect(existsSync(resolve(resourcesDir, 'icon.ico'))).toBe(true)
    expect(existsSync(resolve(resourcesDir, 'icon.png'))).toBe(true)
  })
})

// =====================================================
// 10. Layout Store Bottom Panel Tab Tests
// =====================================================
describe('Layout Store Bottom Panel Tab', () => {
  test('bottomPanelTab defaults to setup', () => {
    useLayoutStore.getState().setBottomPanelTab('setup')
    expect(useLayoutStore.getState().bottomPanelTab).toBe('setup')
  })

  test('setBottomPanelTab changes the active tab', () => {
    useLayoutStore.getState().setBottomPanelTab('run')
    expect(useLayoutStore.getState().bottomPanelTab).toBe('run')

    useLayoutStore.getState().setBottomPanelTab('terminal')
    expect(useLayoutStore.getState().bottomPanelTab).toBe('terminal')
  })
})

// =====================================================
// 11. Cross-Feature Interaction Tests
// =====================================================
describe('Cross-Feature Interactions', () => {
  test('Script store and status store are independent', () => {
    // Setting script state shouldn't affect status store
    useScriptStore.getState().setRunRunning('wt-cross-1', true)
    expect(useWorktreeStatusStore.getState().sessionStatuses).toEqual({})

    // Setting status shouldn't affect script store
    useWorktreeStatusStore.getState().setSessionStatus('s-cross-1', 'working')
    expect(useScriptStore.getState().getScriptState('wt-cross-1').runRunning).toBe(true)
  })

  test('Layout store tab state does not interfere with sidebar state', () => {
    useLayoutStore.getState().setBottomPanelTab('run')
    expect(useLayoutStore.getState().leftSidebarCollapsed).toBe(false)
    expect(useLayoutStore.getState().rightSidebarCollapsed).toBe(false)
  })
})

// =====================================================
// 12. Lint and Typecheck Proxy Tests
// =====================================================
describe('Build Quality', () => {
  test('All Phase 5 stores export correctly', () => {
    expect(useScriptStore).toBeDefined()
    expect(useWorktreeStatusStore).toBeDefined()
    expect(useLayoutStore).toBeDefined()
  })

  test('DEFAULT_SHORTCUTS has no duplicate IDs', () => {
    const ids = DEFAULT_SHORTCUTS.map((s) => s.id)
    const uniqueIds = new Set(ids)
    expect(ids.length).toBe(uniqueIds.size)
  })

  test('Schema version is 4', async () => {
    const version = await mockDb.schemaVersion()
    expect(version).toBe(4)
  })
})
