// AppShell 组件单测
// 依据：02-architecture-design.md §6 应用外壳、§15 AppShell 设计
// 覆盖：导航项按权限过滤 / 当前路径高亮 / 登出按钮 / 用户名显示

import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { AppShell } from "./AppShell";
import type { UserSession } from "@/types";

// mock next/navigation 的 usePathname
const mockUsePathname = vi.fn();
vi.mock("next/navigation", () => ({
  usePathname: () => mockUsePathname(),
}));

// mock next/link 为普通 <a>
vi.mock("next/link", () => ({
  default: ({
    href,
    children,
    ...rest
  }: {
    href: string;
    children: React.ReactNode;
    [key: string]: unknown;
  }) => (
    <a href={href} {...rest}>
      {children}
    </a>
  ),
}));

// mock ChildSwitcher（已在其他测试文件覆盖）
vi.mock("./ChildSwitcher", () => ({
  ChildSwitcher: () => <div data-testid="child-switcher" />,
}));

// mock usePermission hook
const mockHasPermission = vi.fn();
vi.mock("@/hooks/usePermission", () => ({
  usePermission: () => ({
    hasPermission: mockHasPermission,
    permissions: [],
    hasAnyPermission: vi.fn(),
    hasAllPermissions: vi.fn(),
    isAuthenticated: true,
  }),
}));

// mock auth：getUser + logout
const mockGetUser = vi.fn();
const mockLogout = vi.fn();
vi.mock("@/lib/auth", () => ({
  getUser: () => mockGetUser(),
  logout: () => mockLogout(),
}));

const mockUser: UserSession = {
  id: "parent-001",
  email: "parent@example.com",
  name: "张父",
  roles: ["parent"],
  permissions: ["DASHBOARD_VIEW"],
  dataScope: "CHILDREN",
  schoolId: "school-001",
};

beforeEach(() => {
  mockUsePathname.mockReturnValue("/parent/dashboard");
  mockGetUser.mockReturnValue(mockUser);
  mockHasPermission.mockReturnValue(true);
  mockLogout.mockReset();
});

describe("AppShell 布局", () => {
  it("渲染头部标题 Edu 家长端", () => {
    render(<AppShell>content</AppShell>);
    expect(screen.getByText("Edu 家长端")).toBeInTheDocument();
  });

  it("渲染 ChildSwitcher", () => {
    render(<AppShell>content</AppShell>);
    expect(screen.getByTestId("child-switcher")).toBeInTheDocument();
  });

  it("渲染用户名", () => {
    render(<AppShell>content</AppShell>);
    expect(screen.getByText("张父")).toBeInTheDocument();
  });

  it("渲染子内容", () => {
    render(
      <AppShell>
        <div>测试内容</div>
      </AppShell>,
    );
    expect(screen.getByText("测试内容")).toBeInTheDocument();
  });

  it("渲染 main 区域（id=main-content）", () => {
    render(<AppShell>content</AppShell>);
    expect(screen.getByRole("main")).toHaveAttribute("id", "main-content");
  });

  it("渲染 nav 导航（aria-label=主导航）", () => {
    render(<AppShell>content</AppShell>);
    expect(
      screen.getByRole("navigation", { name: "主导航" }),
    ).toBeInTheDocument();
  });
});

describe("AppShell 导航项过滤", () => {
  it("所有权限通过时渲染全部 12 个导航项", () => {
    mockHasPermission.mockReturnValue(true);
    render(<AppShell>content</AppShell>);
    // 限定在 nav 导航区域内计数（排除 header 中的 logo 链接）
    const navList = screen.getByRole("navigation", { name: "主导航" });
    const navItems = navList.querySelectorAll("a");
    expect(navItems.length).toBe(12);
  });

  it("权限全部拒绝时不渲染任何导航项", () => {
    mockHasPermission.mockReturnValue(false);
    render(<AppShell>content</AppShell>);
    const navList = screen.getByRole("navigation", { name: "主导航" });
    const navItems = navList.querySelectorAll("a");
    expect(navItems.length).toBe(0);
  });

  it("仅 DASHBOARD_VIEW 权限通过时只渲染仪表盘", () => {
    mockHasPermission.mockImplementation(
      (perm: string) => perm === "DASHBOARD_VIEW",
    );
    render(<AppShell>content</AppShell>);
    const navList = screen.getByRole("navigation", { name: "主导航" });
    const navItems = navList.querySelectorAll("a");
    expect(navItems.length).toBe(1);
    expect(navItems[0]).toHaveTextContent("仪表盘");
  });

  it("导航项标签包含所有预期项", () => {
    mockHasPermission.mockReturnValue(true);
    render(<AppShell>content</AppShell>);
    const labels = [
      "仪表盘",
      "成绩",
      "考试",
      "考勤",
      "班级",
      "作业",
      "学情",
      "学习路径",
      "趋势",
      "通知",
      "偏好",
      "设置",
    ];
    for (const label of labels) {
      expect(screen.getByText(label)).toBeInTheDocument();
    }
  });
});

describe("AppShell 当前路径高亮", () => {
  it("当前路径 /parent/dashboard 的导航项 aria-current=page", () => {
    mockUsePathname.mockReturnValue("/parent/dashboard");
    mockHasPermission.mockReturnValue(true);
    render(<AppShell>content</AppShell>);
    const activeLink = screen.getByRole("link", { name: "仪表盘" });
    expect(activeLink).toHaveAttribute("aria-current", "page");
  });

  it("非当前路径的导航项 aria-current 为 undefined", () => {
    mockUsePathname.mockReturnValue("/parent/dashboard");
    mockHasPermission.mockReturnValue(true);
    render(<AppShell>content</AppShell>);
    const inactiveLink = screen.getByRole("link", { name: "成绩" });
    expect(inactiveLink).not.toHaveAttribute("aria-current");
  });

  it("切换到 /parent/grades 时成绩导航项高亮", () => {
    mockUsePathname.mockReturnValue("/parent/grades");
    mockHasPermission.mockReturnValue(true);
    render(<AppShell>content</AppShell>);
    const gradesLink = screen.getByRole("link", { name: "成绩" });
    expect(gradesLink).toHaveAttribute("aria-current", "page");
    const dashboardLink = screen.getByRole("link", { name: "仪表盘" });
    expect(dashboardLink).not.toHaveAttribute("aria-current");
  });
});

describe("AppShell 登出", () => {
  it("渲染退出登录按钮（aria-label）", () => {
    render(<AppShell>content</AppShell>);
    expect(screen.getByLabelText("退出登录")).toBeInTheDocument();
  });

  it("点击退出按钮调用 logout", async () => {
    const user = userEvent.setup();
    render(<AppShell>content</AppShell>);
    await user.click(screen.getByLabelText("退出登录"));
    expect(mockLogout).toHaveBeenCalledTimes(1);
  });
});

describe("AppShell 用户信息缺失", () => {
  it("getUser 返回 null 时不崩溃（用户名不显示）", () => {
    mockGetUser.mockReturnValue(null);
    render(<AppShell>content</AppShell>);
    expect(screen.getByText("Edu 家长端")).toBeInTheDocument();
  });
});
