// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react';

vi.mock('../../src/renderer/inbox/api.js', () => ({
  notebookApi: {
    list: vi.fn(async () => [])
  },
  inboxApi: {
    listNotes: vi.fn(async () => []),
    listByStatus: vi.fn(async () => []),
    countsByStatus: vi.fn(async () => ({ active: 0, completed: 0, trashed: 0 })),
    getContinuity: vi.fn(async () => ({
      weekStart: '', weekCount: 0, weekTarget: 7,
      consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null
    })),
    getPendingCount: vi.fn(async () => 0),
    getOllamaStatus: vi.fn(async () => ({ ok: true })),
    getTodayCount: vi.fn(async () => 0),
    getTrashCount: vi.fn(async () => 0),
    listExpired: vi.fn(async () => []),
    getFailedCount: vi.fn(async () => 0),
    listRecallCandidate: vi.fn(async () => null),
    onNoteUpdated: vi.fn(() => () => undefined),
    onOllamaStatus: vi.fn(() => () => undefined),
    onNavigate: vi.fn(() => () => undefined),
    // 4 섹션 mounted 시 호출되는 stub
    loadOllamaSettings: vi.fn(async () => ({ endpoint: '', model: '' })),
    saveOllamaSettings: vi.fn(async () => ({ ok: true })),
    ollamaRecheck: vi.fn(async () => ({ ok: true })),
    getAutostart: vi.fn(async () => ({
      openAtLogin: false,
      diagnostic: {
        withArgs: { openAtLogin: false, executableWillLaunchAtLogin: false },
        noArgs: { openAtLogin: false, executableWillLaunchAtLogin: false },
        execPath: '/p'
      }
    })),
    setAutostart: vi.fn(async () => ({
      openAtLogin: false,
      diagnostic: {
        withArgs: { openAtLogin: false, executableWillLaunchAtLogin: false },
        noArgs: { openAtLogin: false, executableWillLaunchAtLogin: false },
        execPath: '/p'
      }
    })),
    runBackup: vi.fn(async () => ({ ok: true })),
    runExport: vi.fn(async () => ({ ok: true })),
    runImport: vi.fn(async () => ({ ok: true })),
    runSync: vi.fn(async () => ({ ok: true })),
    runExportTelemetry: vi.fn(async () => ({ ok: true })),
    getAppInfo: vi.fn(async () => ({ version: '0.2.7', electron: '?', node: '?', os: '?', profileDir: '?' })),
    openProfileDir: vi.fn(async () => undefined),
    copyAppInfo: vi.fn(async () => undefined),
    // v0.2.9 Cut B Task 12 — onboarding wizard 분기. default 는 onboarding_completed=true 라 wizard 미표시.
    getSettings: vi.fn(async () => ({ onboarding_completed: true })),
    setAiEnabled: vi.fn(async () => ({ ok: true as const })),
    setOnboardingCompleted: vi.fn(async () => ({ ok: true as const })),
    // v0.2.9 Cut B Task 16 — AiProviderSection 가 SettingsPage 렌더 시 호출.
    getDisabledCount: vi.fn(async () => 0),
    enqueueDisabled: vi.fn(async () => ({ count: 0 })),
    // v0.3.0 Cut E — SyncSection 이 SettingsPage 에 마운트되어 호출.
    getSyncStatus: vi.fn(async () => ({ lastAt: null, lastResult: null, nextAt: null })),
    setSyncAutoEnabled: vi.fn(async () => ({ ok: true as const })),
    setSyncIntervalMin: vi.fn(async () => ({ ok: true as const })),
    configureSync: vi.fn(async () => ({ ok: true as const })),
    testSyncConnection: vi.fn(async () => ({ ok: true as const })),
    // v0.3.1 Cut F — VisionSection 이 AiProviderSection 에 마운트되어 호출.
    getVisionModels: vi.fn(async () => ({ models: [], at: null, selected: null })),
    setVisionModel: vi.fn(async () => ({ ok: true as const })),
    refreshVisionCache: vi.fn(async () => ({ ok: true as const, models: [] })),
    // v0.4 Task 15 — loadPromotionCandidates 초기화 stub.
    listPromotionCandidates: vi.fn(async () => []),
    getPromotionDismissedTags: vi.fn(async () => []),
    getPromotionSnoozeUntil: vi.fn(async () => 0)
  }
}));

import { App } from '../../src/renderer/inbox/App';
import { useInbox } from '../../src/renderer/inbox/store';
import { inboxApi } from '../../src/renderer/inbox/api.js';

describe('App — settings view', () => {
  beforeEach(() => {
    cleanup();
    useInbox.setState({
      view: 'inbox',
      counts: { active: 0, completed: 0, archived: 0, trashed: 0 },
      showSettings: false, showTrash: false,
      notes: [], trashNotes: [], trashCount: 0,
      sidebarVisible: false, notebooks: [], promotionCandidates: []
    });
  });

  it('renders SettingsPage when showSettings=true', async () => {
    useInbox.setState({ showSettings: true });
    render(<App />);
    expect(await screen.findByText('설정')).toBeInTheDocument();
    expect(screen.getByText('AI 제공자')).toBeInTheDocument();
  });

  it('header gear icon click sets showSettings=true', async () => {
    render(<App />);
    fireEvent.click(await screen.findByLabelText('설정 열기'));
    expect(useInbox.getState().showSettings).toBe(true);
  });

  it('inbox:navigate "settings" event sets showSettings=true', async () => {
    const navHandlers: Array<(view: 'inbox' | 'trash' | 'settings') => void> = [];
    vi.mocked(inboxApi.onNavigate).mockImplementation((cb) => {
      navHandlers.push(cb);
      return () => {
        const i = navHandlers.indexOf(cb);
        if (i >= 0) navHandlers.splice(i, 1);
      };
    });
    render(<App />);
    await waitFor(() => expect(navHandlers.length).toBeGreaterThan(0));
    navHandlers.forEach((h) => h('settings'));
    await waitFor(() => expect(useInbox.getState().showSettings).toBe(true));
  });
});

describe('App header — 3 tabs (v0.4)', () => {
  beforeEach(() => {
    cleanup();
    useInbox.setState({
      view: 'inbox',
      counts: { active: 5, completed: 3, archived: 2, trashed: 1 },
      notes: [], trashNotes: [], trashCount: 0,
      showTrash: false, showSettings: false,
      sidebarVisible: false, notebooks: [], promotionCandidates: []
    });
    // loadInitial 이 비동기로 counts 를 덮어씀 — onboarding wizard async gate (Task 12) 도입
    // 후 render 가 await 후 발생하므로 mock 의 countsByStatus 가 테스트 기대값을 반환하도록 갱신.
    // v0.4 — countsByStatus 응답에서 archived 제거 (store 가 archived:0 fallback 추가).
    vi.mocked(inboxApi.countsByStatus).mockResolvedValue({ active: 5, completed: 3, trashed: 1 });
  });

  it('renders 3 tabs (Inbox/완료/휴지통) with counts', async () => {
    render(<App />);
    expect(await screen.findByRole('tab', { name: /Inbox\(5\)/ })).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: /완료\(3\)/ })).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: /휴지통\(1\)/ })).toBeInTheDocument();
  });

  it('보관 탭이 헤더에 없음', async () => {
    render(<App />);
    await screen.findByRole('tab', { name: /Inbox/ });
    expect(screen.queryByRole('tab', { name: /보관/ })).toBeNull();
  });

  it('clicking 완료 tab sets view=completed', async () => {
    render(<App />);
    fireEvent.click(await screen.findByRole('tab', { name: /완료/ }));
    expect(useInbox.getState().view).toBe('completed');
  });

  it('inbox tab has aria-selected="true" when active', async () => {
    render(<App />);
    const inboxTab = await screen.findByRole('tab', { name: /Inbox/ });
    expect(inboxTab).toHaveAttribute('aria-selected', 'true');
  });

  it('Cmd+B 키 이벤트가 toggleSidebar 호출', async () => {
    const initialVisible = useInbox.getState().sidebarVisible;
    render(<App />);
    await screen.findByRole('tab', { name: /Inbox/ });
    // jsdom 에서 navigator.platform = '' → isMac=false → ctrlKey 로 판단.
    window.dispatchEvent(new KeyboardEvent('keydown', { key: 'b', ctrlKey: true, bubbles: true }));
    // toggleSidebar 가 호출되면 sidebarVisible 이 반전됨.
    expect(useInbox.getState().sidebarVisible).toBe(!initialVisible);
  });

  it('Sidebar 컴포넌트가 렌더 트리에 포함됨 (sidebarVisible=true)', async () => {
    useInbox.setState({ sidebarVisible: true, notebooks: [] });
    render(<App />);
    await screen.findByRole('tab', { name: /Inbox/ });
    // Sidebar renders an <aside> element when visible
    expect(document.querySelector('aside')).not.toBeNull();
  });
});

describe('App — onboarding wizard', () => {
  beforeEach(() => {
    cleanup();
    useInbox.setState({
      view: 'inbox',
      counts: { active: 0, completed: 0, archived: 0, trashed: 0 },
      showSettings: false, showTrash: false,
      notes: [], trashNotes: [], trashCount: 0,
      sidebarVisible: false, notebooks: [], promotionCandidates: []
    });
    // 각 테스트가 getSettings 의 default mock 을 직접 override.
    vi.mocked(inboxApi.getSettings).mockResolvedValue({ onboarding_completed: true });
  });

  it('renders OnboardingWizard when onboarding_completed=false', async () => {
    vi.mocked(inboxApi.getSettings).mockResolvedValue({ onboarding_completed: false });
    render(<App />);
    await screen.findByText(/Inkling 사용 시작/);
    expect(screen.getByRole('dialog', { name: /시작 안내/ })).toBeInTheDocument();
  });

  it('does not render OnboardingWizard when onboarding_completed=true', async () => {
    vi.mocked(inboxApi.getSettings).mockResolvedValue({ onboarding_completed: true });
    render(<App />);
    await screen.findByRole('tab', { name: /Inbox/ });
    expect(screen.queryByText(/Inkling 사용 시작/)).toBeNull();
  });
});
