import "server-only"

import { and, desc, eq, gt, isNull, lt, or, sql } from "drizzle-orm"
import { createId } from "@paralleldrive/cuid2"

import { db } from "@/shared/db"
import { classes, classInvitationCodes } from "@/shared/db/schema"

/**
 * 班级邀请码 data-access（v3 新增，对标 Google Classroom / 钉钉教育 / 智学网）。
 *
 * 设计要点：
 * - 6 位字母数字（剔除歧义字符 0/O/1/I/L），空间 22^6 ≈ 1.13 亿
 * - 支持有效期（expires_at）与次数限制（max_uses）
 * - 软删除：revoke 时设置 status=disabled + revoked_at
 * - 懒清理：validate 时若发现已过期/已用尽，顺手更新 status
 *
 * 与 classes.invitationCode 的兼容：
 * - validateInvitationCode 优先查新表，未命中时 fallback 到 classes.invitationCode（旧 6 位数字码）
 * - 下个版本移除 fallback
 */

/** 剔除歧义字符（0/O/1/I/L/2/Z/5/S/8/B）后的字符集，共 22 个字符 */
const CODE_CHARSET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789".split("")
const CODE_LENGTH = 6

/** 邀请码状态 */
export type InvitationCodeStatus = "active" | "disabled" | "expired" | "exhausted"

/** 邀请码记录（列表/详情用） */
export interface InvitationCodeRecord {
  id: string
  classId: string
  code: string
  status: InvitationCodeStatus
  maxUses: number | null
  usedCount: number
  expiresAt: Date | null
  createdBy: string
  createdAt: Date
  revokedAt: Date | null
  revokedBy: string | null
  note: string | null
}

/** 生成邀请码选项 */
export interface GenerateCodeOptions {
  /** 有效期（小时），null=永久 */
  expiresInHours?: number | null
  /** 最大使用次数，null=无限 */
  maxUses?: number | null
  /** 备注 */
  note?: string | null
}

/** 校验结果 */
export interface ValidationResult {
  valid: boolean
  classId?: string
  codeId?: string
  reason?: "not_found" | "expired" | "exhausted" | "disabled"
}

/**
 * 生成 6 位邀请码（剔除歧义字符）。
 * 空间：22^6 ≈ 1.13 亿（vs 旧 10^6 = 100 万，提升 113 倍）。
 */
export function generateCode(): string {
  let code = ""
  for (let i = 0; i < CODE_LENGTH; i += 1) {
    code += CODE_CHARSET[Math.floor(Math.random() * CODE_CHARSET.length)]
  }
  return code
}

/**
 * 归一化用户输入的邀请码：
 * - 大写化
 * - 剔除空白
 * - 兼容旧 6 位数字码（fallback 用）
 */
export function normalizeCode(input: string): string {
  return input.trim().toUpperCase()
}

/**
 * 判断是否为合法的新格式邀请码（6 位字母数字，剔除歧义字符）。
 */
export function isNewFormatCode(code: string): boolean {
  const normalized = normalizeCode(code)
  if (normalized.length !== CODE_LENGTH) return false
  for (const ch of normalized) {
    if (!CODE_CHARSET.includes(ch)) return false
  }
  return true
}

/**
 * 判断是否为旧格式邀请码（6 位数字，fallback 用）。
 */
export function isLegacyFormatCode(code: string): boolean {
  return /^\d{6}$/.test(code.trim())
}

// ============ Legacy 6 位数字邀请码（classes.invitationCode） ============

/**
 * 判断错误是否为邀请码重复错误（旧 6 位数字码，classes.invitationCode 唯一约束）。
 */
export const isDuplicateInvitationCodeError = (err: unknown): boolean => {
  if (!err) return false
  const msg = err instanceof Error ? err.message : String(err)
  const m = msg.toLowerCase()
  return m.includes("duplicate") && (m.includes("invitation") || m.includes("invitation_code"))
}

const generateInvitationCode = (): string => {
  const n = Math.floor(Math.random() * 1_000_000)
  return String(n).padStart(6, "0")
}

/**
 * 生成唯一邀请码（旧 6 位数字格式，classes.invitationCode）。
 * DB unique 约束 + 40 次重试。
 */
export const generateUniqueInvitationCode = async (): Promise<string> => {
  for (let attempt = 0; attempt < 40; attempt += 1) {
    const code = generateInvitationCode()
    const [existing] = await db
      .select({ id: classes.id })
      .from(classes)
      .where(eq(classes.invitationCode, code))
      .limit(1)
    if (!existing) return code
  }
  throw new Error("Failed to generate invitation code")
}

// ============ 新格式邀请码（class_invitation_codes 表） ============

/**
 * 生成唯一邀请码（带重试）。
 * DB unique 约束 + 40 次重试（沿用现有模式）。
 */
export async function generateUniqueCode(): Promise<string> {
  for (let attempt = 0; attempt < 40; attempt += 1) {
    const code = generateCode()
    const [existing] = await db
      .select({ id: classInvitationCodes.id })
      .from(classInvitationCodes)
      .where(eq(classInvitationCodes.code, code))
      .limit(1)
    if (!existing) return code
  }
  throw new Error("Failed to generate invitation code")
}

/**
 * 为班级创建邀请码。
 *
 * @param classId 班级 ID
 * @param createdBy 创建人 ID（管理员/教师）
 * @param opts 可选：有效期/次数/备注
 */
export async function createInvitationCode(
  classId: string,
  createdBy: string,
  opts: GenerateCodeOptions = {}
): Promise<InvitationCodeRecord> {
  const code = await generateUniqueCode()
  const expiresAt = opts.expiresInHours
    ? new Date(Date.now() + opts.expiresInHours * 60 * 60 * 1000)
    : null

  const id = createId()
  await db.insert(classInvitationCodes).values({
    id,
    classId,
    code,
    status: "active",
    maxUses: opts.maxUses ?? null,
    usedCount: 0,
    expiresAt,
    createdBy,
    note: opts.note ?? null,
  })

  const [record] = await db
    .select()
    .from(classInvitationCodes)
    .where(eq(classInvitationCodes.id, id))
    .limit(1)

  if (!record) throw new Error("Failed to create invitation code")
  return mapRecord(record)
}

/**
 * 获取班级当前有效的邀请码（取最新一个 active 的）。
 * 给 onboarding/join 用，若无则返回 null。
 */
export async function getActiveInvitationCode(classId: string): Promise<InvitationCodeRecord | null> {
  const [record] = await db
    .select()
    .from(classInvitationCodes)
    .where(
      and(
        eq(classInvitationCodes.classId, classId),
        eq(classInvitationCodes.status, "active"),
        or(isNull(classInvitationCodes.expiresAt), gt(classInvitationCodes.expiresAt, new Date()))
      )
    )
    .orderBy(desc(classInvitationCodes.createdAt))
    .limit(1)

  return record ? mapRecord(record) : null
}

/**
 * 列出班级所有邀请码（管理端用）。
 */
export async function listClassInvitationCodes(classId: string): Promise<InvitationCodeRecord[]> {
  const records = await db
    .select()
    .from(classInvitationCodes)
    .where(eq(classInvitationCodes.classId, classId))
    .orderBy(desc(classInvitationCodes.createdAt))

  return records.map(mapRecord)
}

/**
 * 校验邀请码有效性（不消耗）。
 *
 * 兼容策略：
 * 1. 优先查新表 class_invitation_codes
 * 2. 未命中且为旧格式（6 位数字）时 fallback 到 classes.invitationCode
 *
 * 懒清理：若发现已过期/已用尽，顺手更新 status。
 */
export async function validateInvitationCode(code: string): Promise<ValidationResult> {
  const normalized = normalizeCode(code)

  // 1. 优先查新表
  const [record] = await db
    .select()
    .from(classInvitationCodes)
    .where(eq(classInvitationCodes.code, normalized))
    .limit(1)

  if (record) {
    // 懒清理：已过期
    if (record.status === "active" && record.expiresAt && record.expiresAt < new Date()) {
      await db
        .update(classInvitationCodes)
        .set({ status: "expired" })
        .where(eq(classInvitationCodes.id, record.id))
      return { valid: false, reason: "expired" }
    }
    // 懒清理：已用尽
    if (
      record.status === "active" &&
      record.maxUses !== null &&
      record.usedCount >= record.maxUses
    ) {
      await db
        .update(classInvitationCodes)
        .set({ status: "exhausted" })
        .where(eq(classInvitationCodes.id, record.id))
      return { valid: false, reason: "exhausted" }
    }
    // 已禁用
    if (record.status !== "active") {
      // P2-A: record.status 经 !== "active" 收窄后为 "disabled" | "expired" | "exhausted"，
      // 均属于 ValidationResult["reason"] 联合类型，无需 as 断言
      return { valid: false, reason: record.status }
    }
    return { valid: true, classId: record.classId, codeId: record.id }
  }

  // 2. Fallback：旧格式 6 位数字码
  if (isLegacyFormatCode(normalized)) {
    const [cls] = await db
      .select({ id: classes.id })
      .from(classes)
      .where(eq(classes.invitationCode, normalized))
      .limit(1)
    if (cls) {
      return { valid: true, classId: cls.id }
    }
  }

  return { valid: false, reason: "not_found" }
}

/**
 * 消耗邀请码（原子性 used_count++）。
 * 在 enrollStudentByInvitationCode / enrollTeacherByInvitationCode 成功后调用。
 */
export async function consumeInvitationCode(code: string): Promise<void> {
  const normalized = normalizeCode(code)
  await db
    .update(classInvitationCodes)
    .set({
      usedCount: sql`${classInvitationCodes.usedCount} + 1`,
    })
    .where(eq(classInvitationCodes.code, normalized))
}

/**
 * 撤销邀请码（软删除）。
 */
export async function revokeInvitationCode(
  codeId: string,
  revokedBy: string
): Promise<void> {
  await db
    .update(classInvitationCodes)
    .set({
      status: "disabled",
      revokedAt: new Date(),
      revokedBy,
    })
    .where(eq(classInvitationCodes.id, codeId))
}

/**
 * 清理已过期/已用尽的邀请码（定时任务用）。
 * 当前采用懒清理，此函数供未来 cron 调用。
 */
export async function purgeExpiredCodes(): Promise<number> {
  const now = new Date()
  const result = await db
    .update(classInvitationCodes)
    .set({ status: "expired" })
    .where(
      and(
        eq(classInvitationCodes.status, "active"),
        or(
          lt(classInvitationCodes.expiresAt, now),
          and(
            isNull(classInvitationCodes.maxUses),
            sql`${classInvitationCodes.usedCount} >= ${classInvitationCodes.maxUses}`
          )
        )
      )
    )

  // MySqlRawQueryResult 是 [rows, fields] 元组，rows 可能含 affectedRows
  const rows = Array.isArray(result) ? result[0] : result
  // P2-A: 使用 in 操作符收窄 unknown，避免 `(rows as { affectedRows: unknown }).affectedRows` 断言
  if (typeof rows === "object" && rows !== null && "affectedRows" in rows) {
    return Number(rows.affectedRows)
  }
  return 0
}

// ============ helpers ============

function mapRecord(row: typeof classInvitationCodes.$inferSelect): InvitationCodeRecord {
  // P2-A: schema 中 status 列为 mysqlEnum，推断类型即 InvitationCodeStatus，无需 as 断言
  return {
    id: row.id,
    classId: row.classId,
    code: row.code,
    status: row.status,
    maxUses: row.maxUses,
    usedCount: row.usedCount,
    expiresAt: row.expiresAt,
    createdBy: row.createdBy,
    createdAt: row.createdAt,
    revokedAt: row.revokedAt,
    revokedBy: row.revokedBy,
    note: row.note,
  }
}
