import { render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ReactNode } from 'react'
import type { Settings, VaultEntry } from './types'
import { DEFAULT_VAULTS } from './hooks/useVaultSwitcher'
import { TooltipProvider } from './components/ui/tooltip'

const localStorageMock = (() => {
  let store: Record<string, string> = {}
  return {
    clear: () => { store = {} },
    getItem: (key: string) => store[key] ?? null,
    removeItem: (key: string) => { delete store[key] },
    setItem: (key: string, value: string) => { store[key] = value },
  }
})()

Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, writable: true })
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation((query: string) => ({
    addEventListener: vi.fn(),
    addListener: vi.fn(),
    dispatchEvent: vi.fn(),
    matches: false,
    media: query,
    onchange: null,
    removeEventListener: vi.fn(),
    removeListener: vi.fn(),
  })),
})

const editorSnapshots = vi.hoisted(() => [] as Array<{
  activeTabPath: string | null
  entryTitles: string[]
}>)

vi.mock('@tauri-apps/api/core', () => ({
  invoke: vi.fn(),
}))

vi.mock('@tauri-apps/api/window', () => ({
  getCurrentWindow: () => ({
    innerSize: vi.fn(async () => ({ toLogical: () => ({ width: 1400, height: 900 }) })),
    scaleFactor: vi.fn(async () => 1),
    setMinSize: vi.fn(async () => {}),
    setSize: vi.fn(async () => {}),
  }),
}))

vi.mock('./components/Editor', () => ({
  Editor: (props: { activeTabPath: string | null; entries: VaultEntry[] }) => {
    const entryTitles = props.entries.map((entry) => entry.title)
    editorSnapshots.push({ activeTabPath: props.activeTabPath, entryTitles })
    return <div data-testid="mock-editor-entry-titles">{entryTitles.join('|')}</div>
  },
}))

vi.mock('./hooks/useUpdater', () => ({
  restartApp: vi.fn(),
  useUpdater: () => ({
    status: { state: 'idle' },
    actions: {
      checkForUpdates: vi.fn(async () => ({ kind: 'up-to-date' })),
      dismiss: vi.fn(),
      openReleaseNotes: vi.fn(),
      startDownload: vi.fn(),
    },
  }),
}))

vi.mock('./utils/ai-chat', async () => {
  const actual = await vi.importActual<typeof import('./utils/ai-chat')>('./utils/ai-chat')
  return {
    ...actual,
    buildSystemPrompt: vi.fn(() => ({ prompt: '', totalTokens: 0, truncated: false })),
    checkClaudeCli: vi.fn(async () => ({ installed: false })),
    streamClaudeChat: vi.fn(async () => 'mock-session'),
  }
})

vi.mock('./utils/streamAiAgent', () => ({
  streamAiAgent: vi.fn(async () => {}),
}))

function makeEntry(overrides: Partial<VaultEntry>): VaultEntry {
  return {
    path: '/vault/note.md',
    filename: 'note.md',
    title: 'Note',
    isA: 'Note',
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    archived: false,
    modifiedAt: 1_700_000_000,
    createdAt: null,
    fileSize: 256,
    snippet: '',
    wordCount: 0,
    relationships: {},
    icon: null,
    color: null,
    order: null,
    sidebarLabel: null,
    template: null,
    sort: null,
    view: null,
    visible: true,
    organized: false,
    favorite: false,
    favoriteIndex: null,
    listPropertiesDisplay: [],
    outgoingLinks: [],
    properties: {},
    hasH1: true,
    fileKind: 'markdown',
    ...overrides,
  }
}

const activeEntry = makeEntry({
  path: '/vault/project/test.md',
  filename: 'test.md',
  title: 'Test Project',
  isA: 'Project',
})
const relatedEntry = makeEntry({
  path: '/vault/topic/dev.md',
  filename: 'dev.md',
  title: 'Software Development',
  isA: 'Topic',
})
const secondEntry = makeEntry({
  path: '/vault/project/second.md',
  filename: 'second.md',
  title: 'Second Project',
  isA: 'Project',
})
const entries = [activeEntry, relatedEntry, secondEntry]
const noteContent = '---\ntitle: Test Project\ntype: Project\n---\n\n# Test Project\n'
const defaultVaultPath = DEFAULT_VAULTS[0].path || '/Users/mock/Documents/Getting Started'

function createSettings(): Settings {
  return {
    anonymous_id: null,
    analytics_enabled: null,
    auto_pull_interval_minutes: null,
    autogit_enabled: false,
    autogit_idle_threshold_seconds: 90,
    autogit_inactive_threshold_seconds: 30,
    crash_reporting_enabled: null,
    release_channel: null,
    telemetry_consent: true,
  }
}

const commandResults: Record<string, unknown> = {}

function resetCommandResults() {
  Object.assign(commandResults, {
    check_vault_exists: true,
    get_all_content: { [activeEntry.path]: noteContent },
    get_default_vault_path: defaultVaultPath,
    get_file_history: [],
    get_modified_files: [],
    get_note_content: vi.fn(() => noteContent),
    get_settings: createSettings(),
    get_vault_settings: { theme: null },
    is_git_repo: true,
    list_themes: [],
    list_vault: vi.fn(() => entries),
    list_vault_folders: [],
    list_views: [],
    load_vault_list: {
      active_vault: '/vault',
      hidden_defaults: [],
      vaults: [{ label: 'Test Vault', path: '/vault' }],
    },
    reload_vault_entry: vi.fn(({ path }: { path: string }) =>
      entries.find((entry) => entry.path === path) ?? null,
    ),
    save_settings: null,
    sync_vault_asset_scope_for_window: null,
  })
}

function resolveCommandResult(command: string, args?: unknown) {
  const result = Reflect.get(commandResults, command) as unknown
  return typeof result === 'function'
    ? (result as (input?: unknown) => unknown)(args)
    : result ?? null
}

vi.mock('./mock-tauri', () => ({
  addMockEntry: vi.fn(),
  isTauri: vi.fn(() => false),
  mockInvoke: vi.fn(async (command: string, args?: unknown) => resolveCommandResult(command, args)),
  trackMockChange: vi.fn(),
  updateMockContent: vi.fn(),
}))

import App from './App'

function renderApp(children: ReactNode) {
  return render(<TooltipProvider>{children}</TooltipProvider>)
}

describe('App note windows', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    editorSnapshots.length = 0
    resetCommandResults()
    localStorage.clear()
    localStorage.setItem('tolaria:claude-code-onboarding-dismissed', '1')
    window.history.replaceState(
      {},
      '',
      '/?window=note&path=%2Fvault%2Fproject%2Ftest.md&vault=%2Fvault&title=Test+Project',
    )
  })

  it('loads the active vault graph in note windows while opening the requested note', async () => {
    renderApp(<App />)

    await waitFor(() => {
      expect(commandResults.reload_vault_entry).toHaveBeenCalledWith({
        path: activeEntry.path,
        vaultPath: '/vault',
      })
    })
    expect(commandResults.list_vault).toHaveBeenCalled()

    await waitFor(() => {
      expect(screen.getByTestId('mock-editor-entry-titles')).toHaveTextContent(
        'Test Project|Software Development|Second Project',
      )
    })
    expect(editorSnapshots.at(-1)).toEqual({
      activeTabPath: activeEntry.path,
      entryTitles: ['Test Project', 'Software Development', 'Second Project'],
    })
  })

  it('opens repeated note windows through the full app vault loader', async () => {
    const firstWindow = renderApp(<App />)

    await waitFor(() => {
      expect(commandResults.reload_vault_entry).toHaveBeenCalledWith({
        path: activeEntry.path,
        vaultPath: '/vault',
      })
    })
    firstWindow.unmount()

    window.history.replaceState(
      {},
      '',
      '/?window=note&path=%2Fvault%2Fproject%2Fsecond.md&vault=%2Fvault&title=Second+Project',
    )
    renderApp(<App />)

    await waitFor(() => {
      expect(commandResults.reload_vault_entry).toHaveBeenCalledWith({
        path: secondEntry.path,
        vaultPath: '/vault',
      })
    })
    expect(commandResults.reload_vault_entry).toHaveBeenCalledTimes(2)
    expect(vi.mocked(commandResults.list_vault).mock.calls.length).toBeGreaterThanOrEqual(2)
  })

  it('probes installed AI agents in note windows for target-picker parity', async () => {
    const getAiAgentsStatus = vi.fn(() => ({
      claude_code: { installed: true, version: '2.1.90' },
      codex: { installed: true, version: '0.122.0-alpha.1' },
      copilot: { installed: true, version: '1.0.58' },
      opencode: { installed: true, version: '0.7.4' },
      pi: { installed: false, version: null },
      antigravity: { installed: true, version: '0.3.2' },
      kiro: { installed: false, version: null },
      hermes: { installed: false, version: null },
    }))
    commandResults.get_ai_agents_status = getAiAgentsStatus

    renderApp(<App />)

    await waitFor(() => {
      expect(getAiAgentsStatus).toHaveBeenCalled()
    })
  })
})
