import "@testing-library/jest-dom/vitest"
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { render, screen, fireEvent, cleanup } from "@testing-library/react"

import { AnnouncementCard } from "./announcement-card"
import { AnnouncementsServiceProvider } from "./announcements-service-context"
import type { Announcement, AnnouncementsService } from "../types"
import type { ActionState } from "@/shared/types/action-state"

// Mock next-intl：返回 key 作为字面量，便于断言
vi.mock("next-intl", () => ({
  useTranslations: () => (key: string, params?: Record<string, string>) => {
    if (!params) return key
    return `${key}:${JSON.stringify(params)}`
  },
}))

// Mock next/navigation：router.refresh 不做任何事
vi.mock("next/navigation", () => ({
  useRouter: () => ({
    refresh: vi.fn(),
    push: vi.fn(),
    replace: vi.fn(),
  }),
}))

// Mock sonner：toast.success / error 收集调用
const toastMocks = { success: vi.fn(), error: vi.fn() }
vi.mock("sonner", () => ({
  toast: {
    success: (...args: unknown[]) => toastMocks.success(...args),
    error: (...args: unknown[]) => toastMocks.error(...args),
  },
}))

// Mock @/shared/lib/utils 的 formatDate 与 cn
vi.mock("@/shared/lib/utils", () => ({
  cn: (...classes: (string | false | null | undefined)[]) =>
    classes.filter(Boolean).join(" "),
  formatDate: (iso: string) => `FMT:${iso}`,
}))

// Mock @/shared/components/ui/card：渲染 children 即可，避免样式依赖
vi.mock("@/shared/components/ui/card", () => ({
  Card: ({ children, className }: { children: React.ReactNode; className?: string }) =>
    <div data-testid="card" className={className}>{children}</div>,
  CardHeader: ({ children }: { children: React.ReactNode }) =>
    <div data-testid="card-header">{children}</div>,
  CardTitle: ({ children }: { children: React.ReactNode }) =>
    <h3 data-testid="card-title">{children}</h3>,
  CardContent: ({ children }: { children: React.ReactNode }) =>
    <div data-testid="card-content">{children}</div>,
}))

// Mock @/shared/components/ui/badge
vi.mock("@/shared/components/ui/badge", () => ({
  Badge: ({
    children,
    variant,
    className,
  }: {
    children: React.ReactNode
    variant?: string
    className?: string
  }) => (
    <span data-testid="badge" data-variant={variant} className={className}>
      {children}
    </span>
  ),
}))

const baseAnnouncement: Announcement = {
  id: "ann-1",
  title: "测试公告标题",
  content: "测试公告内容",
  type: "school",
  status: "published",
  targetGradeId: null,
  targetClassId: null,
  authorId: "user-1",
  authorName: "管理员",
  publishedAt: "2026-06-25T00:00:00.000Z",
  isPinned: false,
  createdAt: "2026-06-25T00:00:00.000Z",
  updatedAt: "2026-06-25T00:00:00.000Z",
}

function createMockService(
  overrides: Partial<AnnouncementsService> = {}
): AnnouncementsService {
  const ok = <T,>(data: T): ActionState<T> => ({
    success: true,
    data,
    message: "ok",
  })
  return {
    create: vi.fn().mockResolvedValue(ok("new-id")),
    update: vi.fn().mockResolvedValue(ok("ann-1")),
    delete: vi.fn().mockResolvedValue(ok("ann-1")),
    publish: vi.fn().mockResolvedValue(ok("ann-1")),
    archive: vi.fn().mockResolvedValue(ok("ann-1")),
    togglePin: vi.fn().mockResolvedValue(ok("ann-1")),
    markRead: vi.fn().mockResolvedValue(ok("ann-1")),
    getReadStatus: vi.fn().mockResolvedValue(ok({})),
    ...overrides,
  }
}

function renderCard(
  service: AnnouncementsService,
  props: Partial<Parameters<typeof AnnouncementCard>[0]> = {}
) {
  return render(
    <AnnouncementsServiceProvider service={service}>
      <AnnouncementCard announcement={baseAnnouncement} canManage {...props} />
    </AnnouncementsServiceProvider>
  )
}

describe("AnnouncementCard", () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  afterEach(() => {
    cleanup()
  })

  describe("基础渲染", () => {
    it("渲染标题与内容", () => {
      const service = createMockService()
      renderCard(service)
      expect(screen.getByText("测试公告标题")).toBeInTheDocument()
      expect(screen.getByText("测试公告内容")).toBeInTheDocument()
    })

    it("渲染类型 badge（school）", () => {
      const service = createMockService()
      renderCard(service)
      const badges = screen.getAllByTestId("badge")
      const types = badges.map((b) => b.textContent)
      expect(types).toContain("type.school")
    })

    it("未提供 href 时不渲染外层 Link", () => {
      const service = createMockService()
      const { container } = renderCard(service, { href: undefined })
      // 没有 <a> 标签
      expect(container.querySelector("a")).toBeNull()
    })
  })

  describe("isRead 视觉区分", () => {
    it("isRead=false 时应用 ring-2 ring-primary/40 类名", () => {
      const service = createMockService()
      const { container } = renderCard(service, { isRead: false })
      const card = container.querySelector('[data-testid="card"]')
      expect(card?.className).toContain("ring-2")
      expect(card?.className).toContain("ring-primary/40")
    })

    it("isRead=true 时不应用 ring 类名", () => {
      const service = createMockService()
      const { container } = renderCard(service, { isRead: true })
      const card = container.querySelector('[data-testid="card"]')
      expect(card?.className).not.toContain("ring-2")
    })

    it("isRead=undefined 时不应用 ring 类名", () => {
      const service = createMockService()
      const { container } = renderCard(service, { isRead: undefined })
      const card = container.querySelector('[data-testid="card"]')
      expect(card?.className).not.toContain("ring-2")
    })

    it("isRead=false 时渲染未读 badge", () => {
      const service = createMockService()
      renderCard(service, { isRead: false })
      const badges = screen.getAllByTestId("badge")
      const texts = badges.map((b) => b.textContent)
      expect(texts).toContain("status.unread")
    })
  })

  describe("isPinned 视觉与交互", () => {
    it("isPinned=true 时渲染已置顶 badge 与 Pin 图标 fill", () => {
      const service = createMockService()
      renderCard(service, {
        announcement: { ...baseAnnouncement, isPinned: true },
      })
      const badges = screen.getAllByTestId("badge")
      const texts = badges.map((b) => b.textContent)
      expect(texts).toContain("status.pinned")
    })

    it("canManage=true 时渲染置顶按钮", () => {
      const service = createMockService()
      renderCard(service)
      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      expect(pinButton).toBeInTheDocument()
    })

    it("canManage=false 时不渲染置顶按钮", () => {
      const service = createMockService()
      render(
        <AnnouncementsServiceProvider service={service}>
          <AnnouncementCard announcement={baseAnnouncement} canManage={false} />
        </AnnouncementsServiceProvider>
      )
      expect(screen.queryByRole("button", { name: "actions.pin" })).toBeNull()
    })

    it("点击置顶按钮调用 service.togglePin", async () => {
      const togglePin = vi.fn().mockResolvedValue({
        success: true,
        data: "ann-1",
        message: "ok",
      } satisfies ActionState<string>)
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      await fireEvent.click(pinButton)

      expect(togglePin).toHaveBeenCalledWith("ann-1")
      // 乐观更新成功后显示 toast
      expect(toastMocks.success).toHaveBeenCalled()
    })

    it("togglePin 返回失败时回滚 isPinned 并显示 toast.error", async () => {
      const togglePin = vi.fn().mockResolvedValue({
        success: false,
        message: "权限不足",
      } satisfies ActionState<string>)
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      await fireEvent.click(pinButton)

      expect(togglePin).toHaveBeenCalled()
      expect(toastMocks.error).toHaveBeenCalledWith("权限不足")
    })

    it("togglePin 抛错时回滚并显示 toast.error", async () => {
      const togglePin = vi.fn().mockRejectedValue(new Error("network"))
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      await fireEvent.click(pinButton)

      expect(togglePin).toHaveBeenCalled()
      expect(toastMocks.error).toHaveBeenCalled()
    })
  })

  describe("a11y（P2-1）", () => {
    it("置顶按钮支持 Enter 键触发", async () => {
      const togglePin = vi.fn().mockResolvedValue({
        success: true,
        data: "ann-1",
        message: "ok",
      } satisfies ActionState<string>)
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      await fireEvent.keyDown(pinButton, { key: "Enter" })

      expect(togglePin).toHaveBeenCalledWith("ann-1")
    })

    it("置顶按钮支持 Space 键触发", async () => {
      const togglePin = vi.fn().mockResolvedValue({
        success: true,
        data: "ann-1",
        message: "ok",
      } satisfies ActionState<string>)
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      await fireEvent.keyDown(pinButton, { key: " " })

      expect(togglePin).toHaveBeenCalledWith("ann-1")
    })

    it("置顶按钮在 toggling 时禁用", async () => {
      // 永不 resolve 的 promise，让 isToggling 永远为 true
      const togglePin = vi.fn().mockReturnValue(new Promise(() => {}))
      const service = createMockService({ togglePin })
      renderCard(service)

      const pinButton = screen.getByRole("button", { name: "actions.pin" })
      fireEvent.click(pinButton)

      // 等待下一帧让 setIsToggling(true) 生效
      await new Promise((r) => setTimeout(r, 0))
      expect(pinButton).toBeDisabled()
    })
  })
})
