import "server-only"

import { cache } from "react"
import { createId } from "@paralleldrive/cuid2"
import { and, count, desc, eq, inArray, or } from "drizzle-orm"

import { db } from "@/shared/db"
import { announcements, announcementReads, users } from "@/shared/db/schema"
import type {
  Announcement,
  AnnouncementInsertData,
  AnnouncementStatus,
  AnnouncementUpdateData,
  GetAnnouncementsParams,
} from "./types"

const toIso = (d: Date | null | undefined): string | null =>
  d ? d.toISOString() : null

const toIsoRequired = (d: Date): string => d.toISOString()

const mapRow = (
  row: {
    id: string
    title: string
    content: string
    type: "school" | "grade" | "class"
    status: "draft" | "published" | "archived"
    targetGradeId: string | null
    targetClassId: string | null
    authorId: string
    authorName: string | null
    publishedAt: Date | null
    isPinned: boolean
    createdAt: Date
    updatedAt: Date
  }
): Announcement => ({
  id: row.id,
  title: row.title,
  content: row.content,
  type: row.type,
  status: row.status,
  targetGradeId: row.targetGradeId,
  targetClassId: row.targetClassId,
  authorId: row.authorId,
  authorName: row.authorName,
  publishedAt: toIso(row.publishedAt),
  isPinned: row.isPinned,
  createdAt: toIsoRequired(row.createdAt),
  updatedAt: toIsoRequired(row.updatedAt),
})

export const getAnnouncements = cache(
  async (params?: GetAnnouncementsParams): Promise<Announcement[]> => {
    const page = Math.max(1, params?.page ?? 1)
    const pageSize = Math.max(1, params?.pageSize ?? 20)
    const offset = (page - 1) * pageSize

    const conditions = []
    if (params?.status) {
      conditions.push(eq(announcements.status, params.status))
    }
    if (params?.type) {
      conditions.push(eq(announcements.type, params.type))
    }

    // 受众过滤：当提供 audience 时，仅返回对该受众可见的公告
    // (type = 'school') OR (type = 'grade' AND target_grade_id = audience.gradeId)
    // OR (type = 'class' AND target_class_id = audience.classId)
    if (params?.audience) {
      const { gradeId, classId } = params.audience
      const gradeClause = gradeId
        ? and(eq(announcements.type, "grade"), eq(announcements.targetGradeId, gradeId))
        : undefined
      const classClause = classId
        ? and(eq(announcements.type, "class"), eq(announcements.targetClassId, classId))
        : undefined
      const orClauses = [
        eq(announcements.type, "school"),
        gradeClause,
        classClause,
      ].filter((c): c is NonNullable<typeof c> => c !== undefined)
      conditions.push(or(...orClauses))
    }

    const rows = await db
      .select({
        id: announcements.id,
        title: announcements.title,
        content: announcements.content,
        type: announcements.type,
        status: announcements.status,
        targetGradeId: announcements.targetGradeId,
        targetClassId: announcements.targetClassId,
        authorId: announcements.authorId,
        authorName: users.name,
        publishedAt: announcements.publishedAt,
        isPinned: announcements.isPinned,
        createdAt: announcements.createdAt,
        updatedAt: announcements.updatedAt,
      })
      .from(announcements)
      .leftJoin(users, eq(users.id, announcements.authorId))
      .where(conditions.length > 0 ? and(...conditions) : undefined)
      .orderBy(desc(announcements.isPinned), desc(announcements.createdAt))
      .limit(pageSize)
      .offset(offset)

    return rows.map(mapRow)
  }
)

export const getAnnouncementById = cache(
  async (id: string): Promise<Announcement | null> => {
    const [row] = await db
      .select({
        id: announcements.id,
        title: announcements.title,
        content: announcements.content,
        type: announcements.type,
        status: announcements.status,
        targetGradeId: announcements.targetGradeId,
        targetClassId: announcements.targetClassId,
        authorId: announcements.authorId,
        authorName: users.name,
        publishedAt: announcements.publishedAt,
        isPinned: announcements.isPinned,
        createdAt: announcements.createdAt,
        updatedAt: announcements.updatedAt,
      })
      .from(announcements)
      .leftJoin(users, eq(users.id, announcements.authorId))
      .where(eq(announcements.id, id))
      .limit(1)

    return row ? mapRow(row) : null
  }
)

export async function insertAnnouncement(
  data: AnnouncementInsertData
): Promise<string> {
  await db.insert(announcements).values({
    id: data.id,
    title: data.title,
    content: data.content,
    type: data.type,
    status: data.status,
    targetGradeId: data.targetGradeId,
    targetClassId: data.targetClassId,
    authorId: data.authorId,
    publishedAt: data.publishedAt,
  })
  return data.id
}

export async function updateAnnouncementById(
  id: string,
  data: AnnouncementUpdateData
): Promise<void> {
  await db
    .update(announcements)
    .set({
      title: data.title,
      content: data.content,
      type: data.type,
      status: data.status,
      targetGradeId: data.targetGradeId,
      targetClassId: data.targetClassId,
      publishedAt: data.publishedAt,
      updatedAt: data.updatedAt,
    })
    .where(eq(announcements.id, id))
}

export async function deleteAnnouncementById(id: string): Promise<void> {
  await db.delete(announcements).where(eq(announcements.id, id))
}

export async function publishAnnouncementById(
  id: string,
  publishedAt: Date
): Promise<void> {
  await db
    .update(announcements)
    .set({
      status: "published",
      publishedAt,
      updatedAt: new Date(),
    })
    .where(eq(announcements.id, id))
}

export async function archiveAnnouncementById(id: string): Promise<void> {
  await db
    .update(announcements)
    .set({
      status: "archived",
      updatedAt: new Date(),
    })
    .where(eq(announcements.id, id))
}

// ---------------------------------------------------------------------------
// V2-P2-13d: 公告置顶
// ---------------------------------------------------------------------------

export async function toggleAnnouncementPin(id: string): Promise<void> {
  // 查询当前置顶状态
  const [row] = await db
    .select({ isPinned: announcements.isPinned })
    .from(announcements)
    .where(eq(announcements.id, id))
    .limit(1)

  if (!row) return

  await db
    .update(announcements)
    .set({ isPinned: !row.isPinned, updatedAt: new Date() })
    .where(eq(announcements.id, id))
}

// ---------------------------------------------------------------------------
// V2-P2-13d: 公告已读回执（announcement_reads 表）
// ---------------------------------------------------------------------------

/**
 * 标记公告为已读（如果尚未标记）。
 * 使用 INSERT IGNORE 语义避免重复插入（依赖唯一索引）。
 */
export async function markAnnouncementAsRead(announcementId: string, userId: string): Promise<void> {
  // 先检查是否已存在已读记录
  const [existing] = await db
    .select({ id: announcementReads.id })
    .from(announcementReads)
    .where(and(eq(announcementReads.announcementId, announcementId), eq(announcementReads.userId, userId)))
    .limit(1)

  if (existing) return

  const id = createId()
  await db.insert(announcementReads).values({
    id,
    announcementId,
    userId,
  })
}

/**
 * 检查用户是否已读指定公告。
 */
export async function isAnnouncementReadByUser(announcementId: string, userId: string): Promise<boolean> {
  const [row] = await db
    .select({ id: announcementReads.id })
    .from(announcementReads)
    .where(and(eq(announcementReads.announcementId, announcementId), eq(announcementReads.userId, userId)))
    .limit(1)
  return !!row
}

/**
 * 获取公告的已读用户数。
 */
export async function getAnnouncementReadCount(announcementId: string): Promise<number> {
  const [row] = await db
    .select({ value: count() })
    .from(announcementReads)
    .where(eq(announcementReads.announcementId, announcementId))
  return Number(row?.value ?? 0)
}

/**
 * 批量获取用户对多个公告的已读状态。
 * 返回 Map<announcementId, boolean>
 */
export async function getAnnouncementReadStatusForUser(
  announcementIds: string[],
  userId: string
): Promise<Map<string, boolean>> {
  if (announcementIds.length === 0) return new Map()

  const rows = await db
    .select({ announcementId: announcementReads.announcementId })
    .from(announcementReads)
    .where(and(eq(announcementReads.userId, userId), inArray(announcementReads.announcementId, announcementIds)))

  const readSet = new Set(rows.map((r) => r.announcementId))
  return new Map(announcementIds.map((id) => [id, readSet.has(id)]))
}

/**
 * 管理端公告列表页编排函数：一次性获取公告列表、年级列表、班级列表。
 * 将原本散落在 page.tsx 中的多模块编排逻辑下沉到 data-access 层，
 * 页面层只需调用单一函数，提升可复用性与可测试性。
 */
export async function getAdminAnnouncementsPageData(status?: AnnouncementStatus): Promise<{
  announcements: Announcement[]
  grades: { id: string; name: string }[]
  classes: { id: string; name: string }[]
}> {
  const { getGrades } = await import("@/modules/school/data-access")
  const { getAdminClasses } = await import("@/modules/classes/data-access")

  const [announcementList, gradeList, classList] = await Promise.all([
    getAnnouncements({ status }),
    getGrades(),
    getAdminClasses(),
  ])

  return {
    announcements: announcementList,
    grades: gradeList.map((g) => ({ id: g.id, name: g.name })),
    classes: classList.map((c) => ({ id: c.id, name: c.name })),
  }
}

/**
 * 管理端公告编辑页编排函数：一次性获取公告详情和年级列表。
 */
export async function getEditAnnouncementPageData(id: string): Promise<{
  announcement: Announcement | null
  grades: { id: string; name: string }[]
}> {
  const { getGrades } = await import("@/modules/school/data-access")

  const [announcement, gradeList] = await Promise.all([
    getAnnouncementById(id),
    getGrades(),
  ])

  return {
    announcement,
    grades: gradeList.map((g) => ({ id: g.id, name: g.name })),
  }
}
