"use server"

import { revalidatePath } from "next/cache"
import { eq } from "drizzle-orm"

import type { ActionState } from "@/shared/types/action-state"
import { requireAuth } from "@/shared/lib/auth-guard"
import { logAudit } from "@/shared/lib/audit-logger"
import { resolveDefaultPath } from "@/shared/lib/route-resolver"
import {
  rateLimit,
  rateLimitKey,
  RATE_LIMIT_RULES,
} from "@/shared/lib/rate-limit"
import { db } from "@/shared/db"
import { users } from "@/shared/db/schema"
import {
  enrollStudentByInvitationCode,
  enrollTeacherByInvitationCode,
} from "@/modules/classes/data-access"
import { DEFAULT_CLASS_SUBJECTS } from "@/modules/classes/types"
import { OnboardingSchema } from "./schema"
import {
  getOnboardingStatus,
  updateUserProfile,
  bindParentToChild,
} from "./data-access"
import type { OnboardingCompleteData, OnboardingFailureItem } from "./types"

/**
 * 查询当前用户 onboarding 状态。
 * 供服务端组件 / 客户端组件读取，决定是否渲染引导流程。
 */
export async function getOnboardingStatusAction(): Promise<
  ActionState<Awaited<ReturnType<typeof getOnboardingStatus>>>
> {
  try {
    const ctx = await requireAuth()
    const status = await getOnboardingStatus(ctx.userId)
    return { success: true, data: status }
  } catch (e) {
    const message = e instanceof Error ? e.message : "Failed to load onboarding status"
    return { success: false, message }
  }
}

/**
 * 完成 onboarding。
 *
 * 安全设计（修复 P0-1 ~ P0-5、P1-5、P2-7，v3 对标 PowerSchool/Veracross/Auth0）：
 * 1. 角色只读：不写 usersToRoles，角色由管理员预分配，服务端仅读取。
 * 2. 班级绑定：学生/教师均调用 modules/classes data-access 的受校验函数。
 * 3. Zod 校验输入。
 * 4. 全部 DB 写入包在事务内，onboardedAt 最后写入。
 * 5. requireAuth 确保登录态。
 *
 * v3 新增修复：
 * - P0-3 教师科目多选：循环为每个科目调用 enrollTeacherByInvitationCode（修复 UI 多选但服务端只取第一个的 bug）。
 * - P0-4 审计日志：onboarding 完成后写 audit_logs（对标 PowerSchool/Veracross/Auth0 Logs）。
 * - P0-5 服务端幂等：开始时检查 users.onboardedAt，若已完成直接返回成功（防止双击重复事务）。
 * - P1-2 局部错误收集：班级码/子女绑定失败时不回滚整个事务，收集失败列表返回前端，成功项保留。
 */
export async function completeOnboardingAction(
  prevState: ActionState<OnboardingCompleteData> | null,
  formData: FormData
): Promise<ActionState<OnboardingCompleteData>> {
  try {
    const ctx = await requireAuth()
    const userId = ctx.userId

    // P0-5 服务端幂等：已完成的用户直接返回成功，防止双击或刷新重复提交
    const [existingUser] = await db
      .select({ onboardedAt: users.onboardedAt })
      .from(users)
      .where(eq(users.id, userId))
      .limit(1)

    if (existingUser?.onboardedAt) {
      const roleNames = ctx.roles
      return {
        success: true,
        data: { defaultPath: resolveDefaultPath(roleNames) },
      }
    }

    const parsed = OnboardingSchema.safeParse({
      name: formData.get("name"),
      phone: formData.get("phone"),
      address: formData.get("address") ?? "",
      classCodes: parseCodes(formData.get("classCodes")),
      teacherSubjects: parseList(formData.get("teacherSubjects")),
      children: parseChildren(formData.get("children")),
    })

    if (!parsed.success) {
      const firstError = parsed.error.issues[0]
      return {
        success: false,
        message: firstError?.message ?? "输入校验失败",
        errors: formatZodErrors(parsed.error),
      }
    }

    const input = parsed.data
    const roleNames = ctx.roles
    const normalizedRoles = roleNames.map((r) => r)

    // 教师任课科目过滤：仅保留系统默认科目
    const validTeacherSubjects = input.teacherSubjects.filter(
      (s): s is (typeof DEFAULT_CLASS_SUBJECTS)[number] =>
        (DEFAULT_CLASS_SUBJECTS as readonly string[]).includes(s)
    )

    // audit-P1-8：家长绑定子女独立速率限制。
    // 防止被撤销子女关系的家长反复尝试枚举三因子（生日 × 手机后4）。
    // 限制每小时 5 次完整 onboarding 提交，远低于 3.65M 组合枚举所需量级。
    // 仅在家长角色且实际提交子女绑定时检查，避免误伤其他角色。
    if (normalizedRoles.includes("parent") && input.children.length > 0) {
      const bindLimit = await rateLimit({
        key: rateLimitKey("onboarding:bind", userId),
        limit: RATE_LIMIT_RULES.ONBOARDING_BIND.limit,
        windowMs: RATE_LIMIT_RULES.ONBOARDING_BIND.windowMs,
      })
      if (!bindLimit.success) {
        await logAudit({
          action: "onboarding.bind_rate_limited",
          module: "onboarding",
          targetId: userId,
          targetType: "user",
          detail: {
            childrenCount: input.children.length,
            retryAfterMs: bindLimit.retryAfterMs,
          },
          status: "failure",
        })
        const retryMinutes = Math.ceil(bindLimit.retryAfterMs / 60_000)
        return {
          success: false,
          message: `子女绑定尝试过于频繁，请 ${retryMinutes} 分钟后再试`,
        }
      }
    }

    // 收集局部失败项（P1-2）：班级码/子女绑定失败不回滚整个事务
    const failures: OnboardingFailureItem[] = []

    // 事务包裹全部写入
    const result = await db.transaction(async (tx) => {
      // 1. 更新基础资料
      await updateUserProfile(userId, {
        name: input.name,
        phone: input.phone,
        address: input.address,
      })

      // 2. 学生：通过邀请码绑定班级（调用 classes data-access，含校验）
      if (normalizedRoles.includes("student")) {
        for (const code of input.classCodes) {
          try {
            await enrollStudentByInvitationCode(userId, code)
          } catch (e) {
            failures.push({
              type: "class_code",
              code,
              message: e instanceof Error ? e.message : "班级码绑定失败",
            })
          }
        }
      }

      // 3. 教师：通过邀请码绑定任课（P0-3 修复：循环为每个科目绑定，不再只取第一个）
      if (normalizedRoles.includes("teacher")) {
        for (const code of input.classCodes) {
          if (validTeacherSubjects.length === 0) {
            // 未指定科目：调用一次让 data-access 自动分配空位科目
            try {
              await enrollTeacherByInvitationCode(userId, code, null)
            } catch (e) {
              failures.push({
                type: "class_code",
                code,
                message: e instanceof Error ? e.message : "班级码绑定失败",
              })
            }
          } else {
            // 指定科目：循环为每个科目绑定（修复 P0-3 UI 多选但服务端只取第一个的 bug）
            for (const subject of validTeacherSubjects) {
              try {
                await enrollTeacherByInvitationCode(userId, code, subject)
              } catch (e) {
                failures.push({
                  type: "class_code",
                  code,
                  message: e instanceof Error ? e.message : "班级码绑定失败",
                })
              }
            }
          }
        }
      }

      // 4. 家长：绑定子女（支持多子女循环绑定，P1-4）
      if (normalizedRoles.includes("parent") && input.children.length > 0) {
        for (const child of input.children) {
          const bindResult = await bindParentToChild({
            parentId: userId,
            childEmail: child.childEmail,
            childBirthDate: child.childBirthDate,
            childPhoneSuffix: child.childPhoneSuffix,
            relation: child.childRelation,
          })
          if ("error" in bindResult) {
            failures.push({
              type: "child_binding",
              code: child.childEmail,
              message: bindResult.error,
            })
          }
        }
      }

      // 5. 最后标记 onboarded（事务内，确保原子性）
      await tx
        .update(users)
        .set({ onboardedAt: new Date() })
        .where(eq(users.id, userId))

      return { defaultPath: resolveDefaultPath(normalizedRoles) }
    })

    // P0-4 审计日志：记录 onboarding 完成（含失败项明细，对标 PowerSchool/Veracross）
    await logAudit({
      action: "onboarding.complete",
      module: "onboarding",
      targetId: userId,
      targetType: "user",
      detail: {
        roles: normalizedRoles,
        failures,
        classCodesCount: input.classCodes.length,
        childrenCount: input.children.length,
      },
      status: failures.length === 0 ? "success" : "failure",
    })

    revalidatePath("/onboarding")

    // 若有局部失败，返回成功但附带失败列表（P1-2）
    if (failures.length > 0) {
      return {
        success: true,
        data: result,
        message: `配置完成，但 ${failures.length} 项绑定失败`,
      }
    }

    return { success: true, data: result }
  } catch (e) {
    const message = e instanceof Error ? e.message : "Onboarding 提交失败"
    return { success: false, message }
  }
}

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

function parseCodes(raw: FormDataEntryValue | null): string[] {
  if (!raw) return []
  const text = String(raw)
  return text
    .split(/[\s,，;；]+/g)
    .map((s) => s.trim())
    .filter(Boolean)
}

function parseList(raw: FormDataEntryValue | null): string[] {
  if (!raw) return []
  const text = String(raw)
  if (!text) return []
  try {
    const arr = JSON.parse(text)
    return Array.isArray(arr) ? arr.map((s) => String(s).trim()).filter(Boolean) : []
  } catch {
    return text
      .split(/[\s,，;；]+/g)
      .map((s) => s.trim())
      .filter(Boolean)
  }
}

/**
 * 解析家长多子女绑定数据（P1-4）。
 * 输入格式：JSON 数组，每项含 childEmail/childBirthDate/childPhoneSuffix/childRelation。
 * 兼容旧格式：若非 JSON 则返回空数组（旧字段已废弃）。
 */
function parseChildren(raw: FormDataEntryValue | null): Array<{
  childEmail: string
  childBirthDate: string
  childPhoneSuffix: string
  childRelation: string
}> {
  if (!raw) return []
  const text = String(raw)
  if (!text) return []
  try {
    const arr = JSON.parse(text)
    if (!Array.isArray(arr)) return []
    return arr
      .filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null)
      .map((item) => ({
        childEmail: String(item.childEmail ?? "").trim(),
        childBirthDate: String(item.childBirthDate ?? "").trim(),
        childPhoneSuffix: String(item.childPhoneSuffix ?? "").trim(),
        childRelation: String(item.childRelation ?? "").trim(),
      }))
      .filter((item) => item.childEmail.length > 0)
  } catch {
    return []
  }
}

function formatZodErrors(error: import("zod").ZodError): Record<string, string[]> {
  const result: Record<string, string[]> = {}
  for (const issue of error.issues) {
    const key = issue.path.join(".") || "_"
    if (!result[key]) result[key] = []
    result[key].push(issue.message)
  }
  return result
}
