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

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

describe('ProjectConfiguration', () => {
  const project = { id: 'prj_config', path: 'D:/repo/config', name: 'config', createdAt: '', updatedAt: '', lastOpenedAt: '' };
  beforeEach(() => {
    vi.clearAllMocks();
    Object.defineProperty(window, 'matchMedia', {
      configurable: true,
      value: vi.fn().mockImplementation((query: string) => ({
        matches: query === '(prefers-reduced-motion: reduce)',
        media: query,
        onchange: null,
        addListener: vi.fn(),
        removeListener: vi.fn(),
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
        dispatchEvent: vi.fn(),
      })),
    });
    const config = createProjectConfig('standard-development');
    useOpencodeStore.setState({ activeProject: project, projects: [project] });
    useProjectConfigStore.setState({ configsByProjectId: { [project.id]: config }, knowledgeByProjectId: { [project.id]: [] }, errorsByProjectId: {}, loadingProjectId: null });
    hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
      if (path === `/api/opencode/projects/config?projectId=${project.id}`) return { status: 'valid', config, knowledgeFiles: [] };
      if (path === '/api/opencode/skills') return { skills: [
        { name: 'youth-plain-language', description: 'Youth language.', location: 'D:/repo/.opencode/skills/youth-plain-language/SKILL.md' },
        { name: 'pm-project-plan', description: 'Plan projects.', location: 'D:/repo/.opencode/skills/pm-project-plan/SKILL.md' },
      ] };
      if (path === '/api/opencode/projects/config' && init?.method === 'PUT') return { success: true, config: { ...(JSON.parse(init.body as string).config), initialized: true }, knowledgeFiles: [] };
      if (path === '/api/opencode/projects/remove' && init?.method === 'POST') return { success: true, removedProjectId: project.id, projects: [], activeProject: null };
      throw new Error(`Unexpected path ${path}`);
    });
  });

  it('renders compact project resources in the requested order and emphasizes fixed-size Agent cards', async () => {
    render(<MemoryRouter><ProjectConfiguration /></MemoryRouter>);
    const heading = await screen.findByText('在这里配置你的项目基础！');
    expect(heading.tagName).toBe('H1');
    expect(heading.parentElement).not.toHaveClass('border-4');
    expect(screen.queryByText('管理资源，配置伙伴，然后开始创作')).not.toBeInTheDocument();
    expect(screen.queryByText(project.path)).not.toBeInTheDocument();
    expect(screen.queryByText('已初始化')).not.toBeInTheDocument();
    expect(screen.queryByText('待初始化')).not.toBeInTheDocument();
    const agentCards = screen.getAllByTestId(/^project-agent-/);
    expect(agentCards).toHaveLength(4);
    expect(screen.getByTestId('agent-cards-scroll-region')).toHaveClass('p-2', '-m-2');
    expect(agentCards[0]).toHaveClass('h-[260px]', 'w-[210px]');
    expect(agentCards[0]?.querySelector('img')).toHaveClass('mx-auto');
    const resourceCards = screen.getAllByTestId(/^resource-card-/);
    expect(resourceCards.map((card) => card.textContent)).toEqual([
      expect.stringContaining('大脑（大语言模型）'),
      expect.stringContaining('工具箱（技能）'),
      expect.stringContaining('笔记本（知识库）'),
    ]);
    expect(screen.queryByTestId('resource-card-publish')).not.toBeInTheDocument();
    expect(screen.queryByTestId('superpowers-card')).not.toBeInTheDocument();

    fireEvent.click(screen.getByTestId('resource-card-skills'));
    expect(screen.getByText('这里展示已安装技能；绑定请进入伙伴配置。')).toBeInTheDocument();
    const superpowersToggle = screen.getByRole('switch', { name: '启用 Superpowers' });
    expect(superpowersToggle).toHaveAttribute('aria-checked', 'true');
    expect(screen.getByText('已启用')).toBeInTheDocument();
    fireEvent.click(superpowersToggle);
    expect(superpowersToggle).toHaveAttribute('aria-checked', 'false');
    expect(screen.getByText('已关闭')).toBeInTheDocument();
    await waitFor(() => expect(hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/opencode/skills')).toHaveLength(2));
    fireEvent.click(screen.getByRole('button', { name: '关闭' }));

    fireEvent.click(screen.getByTestId('resource-card-models'));
    expect(screen.getByRole('heading', { name: '大脑（大语言模型）', level: 2 })).toBeInTheDocument();
    expect(screen.getByText('项目默认模型')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: '关闭' }));
    await waitFor(() => expect(screen.queryByRole('heading', { name: '大脑（大语言模型）', level: 2 })).not.toBeInTheDocument());
  });

  it('renders the promotion Agent last for a game project', async () => {
    const gameConfig = createProjectConfig('game-development');
    useProjectConfigStore.setState({ configsByProjectId: { [project.id]: gameConfig }, knowledgeByProjectId: { [project.id]: [] }, errorsByProjectId: {}, loadingProjectId: null });
    hostApiFetchMock.mockImplementation(async (path: string) => {
      if (path === `/api/opencode/projects/config?projectId=${project.id}`) return { status: 'valid', config: gameConfig, knowledgeFiles: [] };
      if (path === '/api/opencode/skills') return { skills: [{ name: 'youth-plain-language', description: 'Youth language.', location: 'D:/repo/.opencode/skills/youth-plain-language/SKILL.md' }] };
      throw new Error(`Unexpected path ${path}`);
    });

    render(<MemoryRouter><ProjectConfiguration /></MemoryRouter>);
    const cards = await screen.findAllByTestId(/^project-agent-/);
    expect(cards).toHaveLength(5);
    expect(cards.at(-1)).toHaveTextContent('运营宣传角色');
  });

  it('edits a partner name beside a readonly role and one prompt module', async () => {
    render(<MemoryRouter><ProjectConfiguration /></MemoryRouter>);
    fireEvent.click((await screen.findAllByTestId(/^project-agent-/))[0]!);
    expect(screen.getByRole('heading', { name: '基础设置' })).toBeInTheDocument();
    expect(screen.queryByLabelText('启用此 Agent')).not.toBeInTheDocument();
    expect(screen.getByRole('heading', { name: 'Agent 提示词' })).toBeInTheDocument();
    expect(screen.getByRole('heading', { name: '绑定技能' })).toBeInTheDocument();
    expect(screen.getByLabelText('搜索技能')).toBeInTheDocument();
    expect(screen.getByLabelText('绑定技能：青少年通俗表达')).toBeDisabled();
    expect(screen.getByText('所有伙伴必需')).toBeInTheDocument();
    expect(screen.getByLabelText('职能名称')).toHaveValue('产品规划');
    expect(screen.getByLabelText('职能名称')).toHaveAttribute('readonly');
    expect((screen.getByLabelText('Agent 提示词') as HTMLTextAreaElement).value).toContain('## 核心职责');
    expect(screen.getByLabelText('模型覆盖（可选）').tagName).toBe('SELECT');
    expect(screen.getAllByRole('button', { name: /^选择头像：/ })).toHaveLength(16);
    fireEvent.click(screen.getByRole('button', { name: '选择头像：像素伙伴 16' }));
    expect(screen.getByRole('button', { name: '选择头像：像素伙伴 16' })).toHaveAttribute('aria-pressed', 'true');
    fireEvent.click(screen.getByRole('button', { name: '完成配置' }));
    await waitFor(() => expect(screen.queryByLabelText('搜索技能')).not.toBeInTheDocument());
  });

  it('completes initialization after all partner names are filled', async () => {
    render(<MemoryRouter><ProjectConfiguration /></MemoryRouter>);
    fireEvent.click(screen.getByTestId('resource-card-skills'));
    fireEvent.click(await screen.findByRole('switch', { name: '启用 Superpowers' }));
    fireEvent.click(screen.getByRole('button', { name: '关闭' }));
    const cards = await screen.findAllByTestId(/^project-agent-/);
    for (const [index, card] of cards.entries()) {
      fireEvent.click(card);
      fireEvent.change(screen.getByLabelText('伙伴名字'), { target: { value: `伙伴${index + 1}` } });
      fireEvent.click(screen.getByRole('button', { name: '关闭' }));
    }
    fireEvent.click(await screen.findByRole('button', { name: '确认并完成初始化' }));
    await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/config', expect.objectContaining({ method: 'PUT', body: expect.stringContaining('"initialized":true') })));
    const saveCall = hostApiFetchMock.mock.calls.find(([path, init]) => path === '/api/opencode/projects/config' && (init as RequestInit | undefined)?.method === 'PUT');
    expect(saveCall?.[1]).toEqual(expect.objectContaining({ body: expect.stringContaining('"superpowersEnabled":false') }));
  });

  it('requires explicit confirmation before removing a project', async () => {
    render(<MemoryRouter><ProjectConfiguration /></MemoryRouter>);
    fireEvent.click(await screen.findByRole('button', { name: '删除项目' }));

    const dialog = screen.getByRole('dialog', { name: `删除项目“${project.name}”？` });
    expect(dialog).toHaveTextContent('不会删除磁盘中的项目文件');
    expect(hostApiFetchMock).not.toHaveBeenCalledWith('/api/opencode/projects/remove', expect.anything());

    fireEvent.click(screen.getByRole('button', { name: '确认删除项目' }));
    await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/remove', {
      method: 'POST',
      body: JSON.stringify({ projectId: project.id }),
    }));
    expect(useOpencodeStore.getState().projects).toEqual([]);
    expect(useOpencodeStore.getState().activeProject).toBeNull();
  });
});
