import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Projects } from '@/pages/Projects';
import { useOpencodeStore } from '@/stores/opencode';
import { createProjectConfig } from '../../shared/project-config';

const hostApiFetchMock = vi.fn();
const invokeIpcMock = vi.fn();

vi.mock('@/lib/host-api', () => ({
  hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
  ensureHostApiToken: vi.fn().mockResolvedValue('test-token'),
}));

vi.mock('@/lib/api-client', () => ({
  invokeIpc: (...args: unknown[]) => invokeIpcMock(...args),
}));

describe('Projects page', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useOpencodeStore.setState({
      status: { state: 'stopped', port: 4096 },
      health: null,
      projects: [],
      activeProject: null,
      sessions: [],
      sessionsByProjectId: {},
      selectedSessionId: null,
      sessionStatuses: {},
      sessionMessages: [],
      sessionMessagesBySessionId: {},
      streamingMessage: null,
      streamingMessagesBySessionId: {},
      streamingTools: [],
      streamingToolsBySessionId: {},
      sendingSessionId: null,
      sendingSessionIds: {},
      queuedSessionPrompts: {},
      runtimeAutoStartAttempted: false,
      loading: false,
      error: null,
    });
  });

  it('renders projects loaded from the opencode project store', async () => {
    hostApiFetchMock.mockResolvedValueOnce({
      projects: [{
        id: 'prj_1',
        path: 'D:/repo/packages/ui',
        name: 'ui',
        createdAt: '2026-05-12T00:00:00.000Z',
        updatedAt: '2026-05-12T00:00:00.000Z',
        lastOpenedAt: '2026-05-12T00:00:00.000Z',
      }],
      activeProject: null,
    });

    render(<Projects />);

    await waitFor(() => expect(screen.getByText('ui')).toBeInTheDocument());
    expect(screen.getByText('D:/repo/packages/ui')).toBeInTheDocument();
    expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects');
  });

  it('opens a folder through the native picker', async () => {
    const project = {
      id: 'prj_2',
      path: 'D:/repo/packages/api',
      name: 'api',
      createdAt: '2026-05-12T00:00:00.000Z',
      updatedAt: '2026-05-12T00:00:00.000Z',
      lastOpenedAt: '2026-05-12T00:00:00.000Z',
    };
    hostApiFetchMock
      .mockResolvedValueOnce({ projects: [], activeProject: null })
      .mockResolvedValueOnce({
        success: true,
        project,
        projects: [project],
        activeProject: project,
      });
    invokeIpcMock.mockResolvedValueOnce({
      canceled: false,
      filePaths: ['D:/repo/packages/api'],
    });

    render(<Projects />);
    await screen.findByText('No projects yet');
    fireEvent.click(screen.getAllByRole('button', { name: /open folder/i })[0]);

    await waitFor(() => {
      expect(invokeIpcMock).toHaveBeenCalledWith('dialog:open', {
        properties: ['openDirectory'],
        title: 'Open Project Folder',
      });
    });
    expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/open', {
      method: 'POST',
      body: JSON.stringify({ path: 'D:/repo/packages/api' }),
    });
  });

  it('does not activate a project whose configuration is missing', async () => {
    const project = {
      id: 'prj_missing_template',
      path: 'D:/repo/packages/api',
      name: 'api',
      createdAt: '2026-05-12T00:00:00.000Z',
      updatedAt: '2026-05-12T00:00:00.000Z',
      lastOpenedAt: '2026-05-12T00:00:00.000Z',
    };
    hostApiFetchMock
      .mockResolvedValueOnce({ projects: [project], activeProject: null })
      .mockResolvedValueOnce({ status: 'missing' });

    render(<Projects />);

    const useButton = await screen.findByRole('button', { name: 'Use' });
    fireEvent.click(useButton);

    await waitFor(() => {
      expect(hostApiFetchMock).toHaveBeenCalledWith(
        `/api/opencode/projects/config?projectId=${encodeURIComponent(project.id)}`,
      );
    });
    expect(useOpencodeStore.getState().activeProject).toBeNull();
    expect(await screen.findByText(/configuration is missing/i)).toBeInTheDocument();
  });

  it('activates a project only after its configuration validates', async () => {
    const project = {
      id: 'prj_valid_template',
      path: 'D:/repo/packages/ui',
      name: 'ui',
      createdAt: '2026-05-12T00:00:00.000Z',
      updatedAt: '2026-05-12T00:00:00.000Z',
      lastOpenedAt: '2026-05-12T00:00:00.000Z',
    };
    const config = { ...createProjectConfig(), initialized: true };
    hostApiFetchMock
      .mockResolvedValueOnce({ projects: [project], activeProject: null })
      .mockResolvedValueOnce({ status: 'valid', config, knowledgeFiles: [] })
      .mockResolvedValueOnce({
        success: true,
        project,
        projects: [project],
        activeProject: project,
      });

    render(<Projects />);

    fireEvent.click(await screen.findByRole('button', { name: 'Use' }));

    await waitFor(() => {
      expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/active', {
        method: 'POST',
        body: JSON.stringify({ projectId: project.id }),
      });
    });
    expect(useOpencodeStore.getState().activeProject).toEqual(project);
  });
});
