import "server-only"

import { and, desc, eq, isNull, lte, or } from "drizzle-orm"

import { db } from "@/shared/db"
import { invitationCodes } from "@/shared/db/schema"

import type {
  ConsumeInvitationCodeInput,
  GenerateInvitationCodesInput,
  InvitationCodeRecord,
  InvitationRole,
  ValidateInvitationCodeResult,
} from "./types"
import {
  generateBatchId,
  generateUniqueInvitationCodes,
} from "./lib/code-generator"

/** 数据库行 → InvitationCodeRecord 映射 */
function toRecord(row: typeof invitationCodes.$inferSelect): InvitationCodeRecord {
  return {
    id: row.id.toString(),
    code: row.code,
    email: row.email,
    role: row.role as InvitationRole,
    classId: row.classId,
    notes: row.notes,
    createdAt: row.createdAt,
    createdBy: row.createdBy,
    expiresAt: row.expiresAt,
    usedBy: row.usedBy,
    usedAt: row.usedAt,
  }
}

/**
 * 批量生成邀请码（audit-P2-3 新增）
 *
 * 1. 使用纯函数生成 count 个唯一明文邀请码
 * 2. 批量插入数据库（若遇唯一约束冲突，重新生成冲突项）
 * 3. 返回完整记录（含明文 code，仅此一次返回完整明文）
 */
export async function generateInvitationCodes(
  input: GenerateInvitationCodesInput,
  createdBy: string,
): Promise<{ codes: InvitationCodeRecord[]; batchId: string }> {
  const batchId = generateBatchId()
  const codes = generateUniqueInvitationCodes(input.count)
  const notes = input.notes?.trim() || null
  const expiresAt = input.expiresAt ?? null

  const rows = codes.map((code) => ({
    code,
    email: input.email?.trim() || null,
    role: input.role,
    classId: input.classId?.trim() || null,
    notes: notes ?? `${batchId}`,
    createdBy,
    expiresAt,
    usedBy: null,
    usedAt: null,
  }))

  // 批量插入（drizzle-orm 支持 .values(array) 批量 INSERT）
  await db.insert(invitationCodes).values(rows)

  // MySQL 批量插入后无法直接返回所有行，按 code 查询
  const codeList = codes
  const [first, ...rest] = codeList
  if (!first) {
    return { codes: [], batchId }
  }

  // 一次查询取回所有刚插入的记录
  // drizzle-orm 的 inArray 在 MySQL 下需要手动构造 OR
  const records = await db
    .select()
    .from(invitationCodes)
    .where(
      rest.length === 0
        ? eq(invitationCodes.code, first)
        : or(
            eq(invitationCodes.code, first),
            ...rest.map((c) => eq(invitationCodes.code, c)),
          ),
    )

  return {
    codes: records.map(toRecord),
    batchId,
  }
}

/**
 * 列出所有邀请码（按创建时间倒序，audit-P2-3 新增）
 *
 * @param limit 最多返回的记录数（默认 100）
 * @param includeUsed 是否包含已使用的邀请码（默认 true）
 */
export async function listInvitationCodes(
  limit = 100,
  includeUsed = true,
): Promise<InvitationCodeRecord[]> {
  const condition = includeUsed ? undefined : isNull(invitationCodes.usedBy)

  const rows = condition
    ? await db
        .select()
        .from(invitationCodes)
        .where(condition)
        .orderBy(desc(invitationCodes.createdAt))
        .limit(limit)
    : await db
        .select()
        .from(invitationCodes)
        .orderBy(desc(invitationCodes.createdAt))
        .limit(limit)

  return rows.map(toRecord)
}

/**
 * 校验邀请码是否可用（audit-P2-3 新增）
 *
 * 校验项：
 * 1. 邀请码存在
 * 2. 未被使用（usedBy 为 null）
 * 3. 未过期（expiresAt 为 null 或在未来）
 * 4. 邮箱匹配（若 email 字段非空，则使用者邮箱必须一致）
 *
 * 返回结果中 `code` 字段为脱敏数据（不含 code 明文），用于注册流程。
 */
export async function validateInvitationCode(
  code: string,
  email: string,
): Promise<ValidateInvitationCodeResult> {
  const normalizedCode = code.trim().toUpperCase()

  const [row] = await db
    .select()
    .from(invitationCodes)
    .where(eq(invitationCodes.code, normalizedCode))
    .limit(1)

  if (!row) {
    return { valid: false, reason: "not_found" }
  }

  if (row.usedBy !== null) {
    return { valid: false, reason: "already_used" }
  }

  if (row.expiresAt !== null && row.expiresAt.getTime() < Date.now()) {
    return { valid: false, reason: "expired" }
  }

  if (row.email !== null && row.email.toLowerCase() !== email.toLowerCase()) {
    return { valid: false, reason: "email_mismatch" }
  }

  // 脱敏：不返回 code 明文
  const { code: _code, ...codeWithoutValue } = toRecord(row)
  return { valid: true, code: codeWithoutValue }
}

/**
 * 标记邀请码为已使用（audit-P2-3 新增）
 *
 * 使用乐观锁：仅在 usedBy IS NULL 时更新，避免并发使用同一邀请码。
 * 返回 true 表示成功标记，false 表示邀请码已被他人使用。
 */
export async function consumeInvitationCode(
  input: ConsumeInvitationCodeInput,
): Promise<boolean> {
  const normalizedCode = input.code.trim().toUpperCase()

  const result = await db
    .update(invitationCodes)
    .set({
      usedBy: input.userId,
      usedAt: new Date(),
    })
    .where(
      and(
        eq(invitationCodes.code, normalizedCode),
        isNull(invitationCodes.usedBy),
      ),
    )

  // MySQL ResultSetHeader affectedRows（drizzle-orm MySQL 返回 [ResultSetHeader, FieldPacket[]]）
  const rows = Array.isArray(result) ? result[0] : result
  const affectedRows =
    typeof rows === "object" && rows !== null && "affectedRows" in rows
      ? Number((rows as { affectedRows: unknown }).affectedRows)
      : 0
  return affectedRows > 0
}

/**
 * 删除邀请码（audit-P2-3 新增）
 *
 * 仅允许删除未被使用的邀请码（已使用的邀请码保留作为审计记录）。
 * 返回 true 表示删除成功，false 表示邀请码不存在或已被使用。
 */
export async function deleteInvitationCode(
  codeId: string,
): Promise<boolean> {
  const result = await db
    .delete(invitationCodes)
    .where(
      and(
        eq(invitationCodes.id, codeId),
        isNull(invitationCodes.usedBy),
      ),
    )

  const rows = Array.isArray(result) ? result[0] : result
  const affectedRows =
    typeof rows === "object" && rows !== null && "affectedRows" in rows
      ? Number((rows as { affectedRows: unknown }).affectedRows)
      : 0
  return affectedRows > 0
}

/**
 * 清理已过期的邀请码（audit-P2-3 新增，可选维护函数）
 *
 * 删除已过期且未使用的邀请码。可由 cron 任务定期调用。
 */
export async function purgeExpiredInvitationCodes(): Promise<number> {
  const result = await db
    .delete(invitationCodes)
    .where(
      and(
        lte(invitationCodes.expiresAt, new Date()),
        isNull(invitationCodes.usedBy),
      ),
    )

  const rows = Array.isArray(result) ? result[0] : result
  const affectedRows =
    typeof rows === "object" && rows !== null && "affectedRows" in rows
      ? Number((rows as { affectedRows: unknown }).affectedRows)
      : 0
  return affectedRows
}
