import { NextResponse } from "next/server"
import { and, desc, eq, like, or, sql } from "drizzle-orm"

import { getAuthContext } from "@/shared/lib/auth-guard"
import { db } from "@/shared/db"
import {
  announcements,
  exams,
  questions,
  textbooks,
} from "@/shared/db/schema"

export const dynamic = "force-dynamic"

type SearchType = "all" | "question" | "textbook" | "exam" | "announcement"

const isSearchType = (v: string): v is SearchType =>
  v === "all" || v === "question" || v === "textbook" || v === "exam" || v === "announcement"

const DEFAULT_PAGE_SIZE = 10

interface SearchResultItem {
  id: string
  title: string
  snippet: string
  type: "question" | "textbook" | "exam" | "announcement"
  href: string
  createdAt: string
}

interface SearchResponse {
  success: boolean
  query: string
  type: SearchType
  results: SearchResultItem[]
  total: number
  page: number
  pageSize: number
}

/**
 * GET /api/search?q=keyword&type=all&page=1
 * 全文检索：questions / textbooks / exams / announcements
 * 按角色过滤：学生只能搜索 textbook 和 announcement
 */
export async function GET(req: Request) {
  try {
    const ctx = await getAuthContext()
    const isStudent = ctx.roles.includes("student") && !ctx.roles.includes("admin") && !ctx.roles.includes("teacher")

    const { searchParams } = new URL(req.url)
    const q = (searchParams.get("q") ?? "").trim()
    const typeRaw = (searchParams.get("type") ?? "all").trim()
    const type: SearchType = isSearchType(typeRaw) ? typeRaw : "all"
    const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1)
    const pageSize = Math.min(
      50,
      Math.max(1, Number(searchParams.get("pageSize") ?? String(DEFAULT_PAGE_SIZE)) || DEFAULT_PAGE_SIZE)
    )

    if (!q) {
      return NextResponse.json<SearchResponse>({
        success: true,
        query: q,
        type,
        results: [],
        total: 0,
        page,
        pageSize,
      })
    }

    const kw = `%${q}%`
    const offset = (page - 1) * pageSize
    const results: SearchResultItem[] = []

    // 并行查询各类型（按角色过滤）
    const tasks: Promise<SearchResultItem[]>[] = []

    // 学生不能搜索题目和考试
    if (!isStudent && (type === "all" || type === "question")) {
      tasks.push(searchQuestions(kw, pageSize))
    }
    if (type === "all" || type === "textbook") {
      tasks.push(searchTextbooks(kw, pageSize))
    }
    // 学生不能搜索考试
    if (!isStudent && (type === "all" || type === "exam")) {
      tasks.push(searchExams(kw, pageSize))
    }
    if (type === "all" || type === "announcement") {
      tasks.push(searchAnnouncements(kw, pageSize))
    }

    const grouped = await Promise.all(tasks)
    for (const group of grouped) {
      results.push(...group)
    }

    // 按 createdAt 降序排序后分页
    results.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
    const total = results.length
    const paged = results.slice(offset, offset + pageSize)

    return NextResponse.json<SearchResponse>({
      success: true,
      query: q,
      type,
      results: paged,
      total,
      page,
      pageSize,
    })
  } catch (e) {
    const message = e instanceof Error ? e.message : "Search failed"
    const status = message.includes("auth_required") ? 401 : 500
    return NextResponse.json({ success: false, message }, { status })
  }
}

async function searchQuestions(kw: string, limit: number): Promise<SearchResultItem[]> {
  try {
    const rows = await db
      .select({
        id: questions.id,
        content: questions.content,
        type: questions.type,
        createdAt: questions.createdAt,
      })
      .from(questions)
      .where(
        or(
          // JSON 内容字段转换为文本进行模糊匹配
          sql`CAST(${questions.content} AS CHAR) LIKE ${kw}`,
          like(sql`CAST(${questions.content} AS CHAR)`, kw)
        )!
      )
      .orderBy(desc(questions.createdAt))
      .limit(limit)

    return rows.map((r) => {
      const text = extractTextFromJson(r.content)
      return {
        id: r.id,
        title: truncate(text, 80) || `Question (${r.type})`,
        snippet: truncate(text, 200),
        type: "question" as const,
        href: `/admin/questions?id=${r.id}`,
        createdAt: r.createdAt.toISOString(),
      }
    })
  } catch {
    return []
  }
}

async function searchTextbooks(kw: string, limit: number): Promise<SearchResultItem[]> {
  try {
    const rows = await db
      .select({
        id: textbooks.id,
        title: textbooks.title,
        subject: textbooks.subject,
        grade: textbooks.grade,
        publisher: textbooks.publisher,
        createdAt: textbooks.createdAt,
      })
      .from(textbooks)
      .where(
        or(
          like(textbooks.title, kw),
          like(textbooks.subject, kw),
          like(textbooks.publisher, kw)
        )!
      )
      .orderBy(desc(textbooks.createdAt))
      .limit(limit)

    return rows.map((r) => ({
      id: r.id,
      title: r.title,
      snippet: [r.subject, r.grade, r.publisher].filter(Boolean).join(" · "),
      type: "textbook" as const,
      href: `/admin/textbooks?id=${r.id}`,
      createdAt: r.createdAt.toISOString(),
    }))
  } catch {
    return []
  }
}

async function searchExams(kw: string, limit: number): Promise<SearchResultItem[]> {
  try {
    const rows = await db
      .select({
        id: exams.id,
        title: exams.title,
        description: exams.description,
        status: exams.status,
        createdAt: exams.createdAt,
      })
      .from(exams)
      .where(
        or(
          like(exams.title, kw),
          like(exams.description, kw)
        )!
      )
      .orderBy(desc(exams.createdAt))
      .limit(limit)

    return rows.map((r) => ({
      id: r.id,
      title: r.title,
      snippet: r.description ?? `Status: ${r.status ?? "draft"}`,
      type: "exam" as const,
      href: `/admin/exams?id=${r.id}`,
      createdAt: r.createdAt.toISOString(),
    }))
  } catch {
    return []
  }
}

async function searchAnnouncements(kw: string, limit: number): Promise<SearchResultItem[]> {
  try {
    const rows = await db
      .select({
        id: announcements.id,
        title: announcements.title,
        content: announcements.content,
        type: announcements.type,
        status: announcements.status,
        createdAt: announcements.createdAt,
      })
      .from(announcements)
      .where(
        and(
          eq(announcements.status, "published"),
          or(
            like(announcements.title, kw),
            like(announcements.content, kw)
          )!
        )
      )
      .orderBy(desc(announcements.createdAt))
      .limit(limit)

    return rows.map((r) => ({
      id: r.id,
      title: r.title,
      snippet: truncate(stripHtml(r.content), 200),
      type: "announcement" as const,
      href: `/announcements/${r.id}`,
      createdAt: r.createdAt.toISOString(),
    }))
  } catch {
    return []
  }
}

function extractTextFromJson(content: unknown): string {
  if (typeof content === "string") return content
  try {
    return JSON.stringify(content)
  } catch {
    return ""
  }
}

function stripHtml(html: string): string {
  return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim()
}

function truncate(s: string, max: number): string {
  const t = s.trim()
  if (t.length <= max) return t
  return t.slice(0, max) + "..."
}
