import "server-only"

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

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

/** P2-2: 用 Drizzle 推导类型替代手写内联类型，避免 schema 变更时类型漂移 */
type AnnouncementRow = typeof announcements.$inferSelect & {
  authorName: string | null
}

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

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

const mapRow = (row: AnnouncementRow): 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.gradeIds)
    // OR (type = 'class' AND target_class_id ∈ audience.classIds)
    //
    // P0-2 修复：audience 升级为数组，支持家长多孩子 / 教师多班级 / 年级主任多年级
    if (params?.audience) {
      const { gradeIds, classIds } = params.audience
      const gradeClause =
        gradeIds.length > 0
          ? and(eq(announcements.type, "grade"), inArray(announcements.targetGradeId, gradeIds))
          : undefined
      const classClause =
        classIds.length > 0
          ? and(eq(announcements.type, "class"), inArray(announcements.targetClassId, classIds))
          : 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)
  }
)

/**
 * P2-6: 统计符合条件的公告总数（用于分页）。
 *
 * 与 `getAnnouncements` 使用相同的过滤条件，但不应用 limit/offset。
 * 仅用户端 `getUserAnnouncementsPageData` 调用；管理端列表暂不分页。
 */
export async function countAnnouncements(
  params?: Omit<GetAnnouncementsParams, "page" | "pageSize">
): Promise<number> {
  const conditions = []
  if (params?.status) {
    conditions.push(eq(announcements.status, params.status))
  }
  if (params?.type) {
    conditions.push(eq(announcements.type, params.type))
  }

  if (params?.audience) {
    const { gradeIds, classIds } = params.audience
    const gradeClause =
      gradeIds.length > 0
        ? and(eq(announcements.type, "grade"), inArray(announcements.targetGradeId, gradeIds))
        : undefined
    const classClause =
      classIds.length > 0
        ? and(eq(announcements.type, "class"), inArray(announcements.targetClassId, classIds))
        : undefined
    const orClauses = [
      eq(announcements.type, "school"),
      gradeClause,
      classClause,
    ].filter((c): c is NonNullable<typeof c> => c !== undefined)
    conditions.push(or(...orClauses))
  }

  const [row] = await db
    .select({ value: count() })
    .from(announcements)
    .where(conditions.length > 0 ? and(...conditions) : undefined)

  return row?.value ?? 0
}

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: 公告置顶（P1-6: 原子化，避免 SELECT-then-UPDATE 的并发 lost update）
// ---------------------------------------------------------------------------

export async function toggleAnnouncementPin(id: string): Promise<void> {
  // 单次原子 UPDATE：is_pinned = NOT is_pinned
  await db
    .update(announcements)
    .set({ isPinned: sql`${announcements.isPinned} = NOT ${announcements.isPinned}`, updatedAt: new Date() })
    .where(eq(announcements.id, id))
}

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

/**
 * 标记公告为已读（幂等）。
 * P1-6: 改用 INSERT ... ON DUPLICATE KEY UPDATE 单次原子操作，
 * 替代原先的 SELECT-then-INSERT，减少一次 DB 往返且天然防并发。
 */
export async function markAnnouncementAsRead(announcementId: string, userId: string): Promise<void> {
  const id = createId()
  await db
    .insert(announcementReads)
    .values({
      id,
      announcementId,
      userId,
    })
    .onDuplicateKeyUpdate({
      set: { announcementId }, // no-op update, only ensures existence
    })
}

/**
 * 检查用户是否已读指定公告。
 */
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 })),
  }
}

/**
 * 管理端公告详情页编排函数（P1-2 新增）。
 *
 * 管理员可查看任意状态的公告，同时返回已读人数用于展示。
 */
export async function getAdminAnnouncementDetailPageData(id: string): Promise<{
  announcement: Announcement | null
  readCount: number
}> {
  const [announcement, readCount] = await Promise.all([
    getAnnouncementById(id),
    getAnnouncementReadCount(id),
  ])

  if (!announcement) return { announcement: null, readCount: 0 }
  return { announcement: { ...announcement, readCount }, readCount }
}

/**
 * 根据当前用户的数据范围解析公告受众信息（gradeIds / classIds 数组）。
 *
 * P0-2 修复：从单值返回升级为数组返回，解决：
 * - 家长多孩子（childrenIds 全部解析，非仅首个）
 * - 年级主任多年级（gradeIds 全部返回，非仅首个）
 * - 教师多班级（classIds 全部返回，非仅首个）
 *
 * - all（管理员）：返回 null（可见所有公告）
 * - grade_managed：返回 dataScope.gradeIds 全量
 * - class_members / class_taught：返回 dataScope.classIds 全量 + 各 class 所属 gradeId 并集
 * - children：遍历所有孩子，收集其活跃 classId 与 gradeId 的并集
 * - owned / 其他：尝试用当前 userId 查询（兼容学生直接访问）
 *
 * 返回 null 表示不按受众过滤（管理员视角）。
 */
export async function resolveUserAudience(
  userId: string,
  dataScope: DataScope
): Promise<UserAudience | null> {
  if (dataScope.type === "all") return null

  if (dataScope.type === "grade_managed") {
    return { gradeIds: [...dataScope.gradeIds], classIds: [] }
  }

  if (dataScope.type === "class_members" || dataScope.type === "class_taught") {
    const classIds = [...dataScope.classIds]
    if (classIds.length === 0) return { gradeIds: [], classIds: [] }
    const { getClassGradeId } = await import("@/modules/classes/data-access")
    const gradeIdResults = await Promise.all(classIds.map((cid) => getClassGradeId(cid)))
    const gradeIdSet = new Set<string>()
    for (const gid of gradeIdResults) {
      if (gid) gradeIdSet.add(gid)
    }
    return { gradeIds: Array.from(gradeIdSet), classIds }
  }

  if (dataScope.type === "children") {
    const childIds = [...dataScope.childrenIds]
    if (childIds.length === 0) return { gradeIds: [], classIds: [] }
    const { getStudentActiveClassId, getStudentActiveGradeId } = await import("@/modules/classes/data-access")
    const [classIdLists, gradeIdLists] = await Promise.all([
      Promise.all(childIds.map((cid) => getStudentActiveClassId(cid))),
      Promise.all(childIds.map((cid) => getStudentActiveGradeId(cid))),
    ])
    const classIdSet = new Set<string>()
    const gradeIdSet = new Set<string>()
    for (const cid of classIdLists) if (cid) classIdSet.add(cid)
    for (const gid of gradeIdLists) if (gid) gradeIdSet.add(gid)
    return {
      gradeIds: Array.from(gradeIdSet),
      classIds: Array.from(classIdSet),
    }
  }

  // owned / 其他：尝试用当前 userId 查询（兼容学生角色直接访问）
  const { getStudentActiveClassId, getStudentActiveGradeId } = await import("@/modules/classes/data-access")
  const [classId, gradeId] = await Promise.all([
    getStudentActiveClassId(userId),
    getStudentActiveGradeId(userId),
  ])
  return {
    gradeIds: gradeId ? [gradeId] : [],
    classIds: classId ? [classId] : [],
  }
}

/**
 * 判断一条公告对给定受众是否可见（P0-1 新增）。
 * - school 类型：对所有受众可见
 * - grade 类型：仅当 targetGradeId ∈ audience.gradeIds
 * - class 类型：仅当 targetClassId ∈ audience.classIds
 * audience 为 null 表示管理员视角，对所有公告可见。
 */
export function isAnnouncementVisibleToAudience(
  announcement: { type: Announcement["type"]; targetGradeId: string | null; targetClassId: string | null },
  audience: UserAudience | null
): boolean {
  if (!audience) return true
  if (announcement.type === "school") return true
  if (announcement.type === "grade") {
    return !!announcement.targetGradeId && audience.gradeIds.includes(announcement.targetGradeId)
  }
  if (announcement.type === "class") {
    return !!announcement.targetClassId && audience.classIds.includes(announcement.targetClassId)
  }
  return false
}

/**
 * 用户端公告详情页编排函数（P0-1 新增）。
 *
 * 在 data-access 层结合当前用户受众过滤，避免越权读取草稿/归档/他人班级公告：
 * - 仅返回 status="published" 的公告
 * - 仅当公告对当前用户受众可见时返回，否则返回 null（路由层应转为 404）
 * - 同时查询当前用户的已读状态与已读总人数，一次返回
 */
export async function getAnnouncementByIdForUser(
  id: string,
  userId: string,
  dataScope: DataScope
): Promise<Announcement | null> {
  const [announcement, audience, isRead, readCount] = await Promise.all([
    getAnnouncementById(id),
    resolveUserAudience(userId, dataScope),
    isAnnouncementReadByUser(id, userId),
    getAnnouncementReadCount(id),
  ])

  if (!announcement) return null
  if (announcement.status !== "published") return null
  if (!isAnnouncementVisibleToAudience(announcement, audience)) return null

  return {
    ...announcement,
    isReadByCurrentUser: isRead,
    readCount,
  }
}

/**
 * 用户端公告列表页编排函数：根据当前用户的数据范围解析受众，
 * 并返回已发布的、对该受众可见的公告列表。
 *
 * V3-P0-2: 将原本在 page.tsx 中的 resolveAudience 业务逻辑下沉到 data-access 层，
 * 页面层只需调用单一函数，符合三层架构规范。
 * P0-2: resolveUserAudience 升级为数组返回，支持多孩子/多班级/多年级。
 */
/**
 * P2-6: 用户端公告列表页分页编排函数。
 *
 * 返回 `{ items, total, page, pageSize }` 供 AnnouncementPagination 组件使用。
 * page 从 1 开始；pageSize 默认 12（卡片网格 3 列 × 4 行）。
 * 内部并行执行列表查询与 count 查询，减少总延迟。
 */
export async function getUserAnnouncementsPageData(
  userId: string,
  dataScope: DataScope,
  page: number = 1,
  pageSize: number = 12
): Promise<PaginatedAnnouncements> {
  const audience = await resolveUserAudience(userId, dataScope)
  const safePage = Math.max(1, page)
  const safePageSize = Math.max(1, pageSize)

  const queryArgs = {
    status: "published" as const,
    audience: audience ?? undefined,
    page: safePage,
    pageSize: safePageSize,
  }

  const [items, total] = await Promise.all([
    getAnnouncements(queryArgs),
    countAnnouncements(queryArgs),
  ])

  return { items, total, page: safePage, pageSize: safePageSize }
}

/**
 * 解析公告的目标用户 ID 列表（P1-5: 从 actions.ts 下沉到 data-access）。
 *
 * 纯业务编排逻辑（聚合跨模块 data-access 的用户查询），独立于 Server Action 编排，
 * 便于单元测试 mock 跨模块 data-access。
 *
 * - school: 全校所有用户
 * - grade: 该年级下所有用户
 * - class: 该班级学生 + 任课教师 + 班主任
 */
export async function resolveAnnouncementTargetUserIds(
  announcement: Announcement
): Promise<string[]> {
  if (announcement.type === "school") {
    const { getAllUserIds } = await import("@/modules/users/data-access")
    return getAllUserIds()
  }

  if (announcement.type === "grade" && announcement.targetGradeId) {
    const { getUserIdsByGradeId } = await import("@/modules/users/data-access")
    return getUserIdsByGradeId(announcement.targetGradeId)
  }

  if (announcement.type === "class" && announcement.targetClassId) {
    const { getStudentIdsByClassId, getTeacherIdsByClassIds } = await import("@/modules/classes/data-access")
    const [studentIds, teacherIds] = await Promise.all([
      getStudentIdsByClassId(announcement.targetClassId),
      getTeacherIdsByClassIds([announcement.targetClassId]),
    ])
    return Array.from(new Set([...studentIds, ...teacherIds]))
  }

  return []
}
