/**
 * 6.5: 单测覆盖权限校验与数据流转
 *
 * 覆盖范围：
 * - question-content-utils 的纯函数（数据流转核心）
 * - exam-homework-role-config 的角色特性合并逻辑（权限校验配置层）
 * - applyAutoGrades 自动判分流程
 */

import { describe, it, expect } from "vitest"
import { isRecord } from "@/shared/lib/type-guards"
import {
  getQuestionText,
  getOptions,
  getChoiceCorrectIds,
  getJudgmentCorrectAnswer,
  getTextCorrectAnswers,
  parseSavedAnswer,
  extractAnswerValue,
  normalizeText,
  isAutoGradable,
  computeIsCorrect,
  getCorrectnessState,
  applyAutoGrades,
  formatStudentAnswer,
  type AutoGradableAnswer,
} from "./question-content-utils"

describe("isRecord", () => {
  it("returns true for non-null objects (including arrays)", () => {
    expect(isRecord({})).toBe(true)
    expect(isRecord({ a: 1 })).toBe(true)
    // Note: arrays are objects in JS, so isRecord returns true for them.
    // Callers that need to exclude arrays should check Array.isArray separately.
    expect(isRecord([1, 2])).toBe(true)
  })
  it("returns false for null and primitives", () => {
    expect(isRecord(null)).toBe(false)
    expect(isRecord(undefined)).toBe(false)
    expect(isRecord("string")).toBe(false)
    expect(isRecord(42)).toBe(false)
  })
})

describe("getQuestionText", () => {
  it("extracts text from valid content", () => {
    expect(getQuestionText({ text: "What is 2+2?" })).toBe("What is 2+2?")
  })
  it("returns empty string for missing or non-string text", () => {
    expect(getQuestionText({})).toBe("")
    expect(getQuestionText({ text: 123 })).toBe("")
    expect(getQuestionText(null)).toBe("")
    expect(getQuestionText("string")).toBe("")
  })
})

describe("getOptions", () => {
  it("parses valid options array", () => {
    const content = {
      options: [
        { id: "a", text: "Option A", isCorrect: true },
        { id: "b", text: "Option B" },
      ],
    }
    const opts = getOptions(content)
    expect(opts).toHaveLength(2)
    expect(opts[0]).toEqual({ id: "a", text: "Option A", isCorrect: true })
    expect(opts[1]).toEqual({ id: "b", text: "Option B", isCorrect: false })
  })
  it("filters out options missing id or text", () => {
    const content = {
      options: [
        { id: "a", text: "Valid" },
        { id: "", text: "No ID" },
        { id: "c", text: "" },
        { id: "d" },
        { text: "No ID" },
      ],
    }
    expect(getOptions(content)).toHaveLength(1)
  })
  it("returns empty array when options is missing or not an array", () => {
    expect(getOptions({})).toEqual([])
    expect(getOptions({ options: "not-array" })).toEqual([])
    expect(getOptions(null)).toEqual([])
  })
})

describe("getChoiceCorrectIds", () => {
  it("returns IDs of options marked isCorrect", () => {
    const content = {
      options: [
        { id: "a", text: "A", isCorrect: true },
        { id: "b", text: "B" },
        { id: "c", text: "C", isCorrect: true },
      ],
    }
    expect(getChoiceCorrectIds(content)).toEqual(["a", "c"])
  })
  it("returns empty array when no correct options", () => {
    const content = { options: [{ id: "a", text: "A" }] }
    expect(getChoiceCorrectIds(content)).toEqual([])
  })
})

describe("getJudgmentCorrectAnswer", () => {
  it("returns boolean when correctAnswer is boolean", () => {
    expect(getJudgmentCorrectAnswer({ correctAnswer: true })).toBe(true)
    expect(getJudgmentCorrectAnswer({ correctAnswer: false })).toBe(false)
  })
  it("returns null for missing or non-boolean", () => {
    expect(getJudgmentCorrectAnswer({})).toBeNull()
    expect(getJudgmentCorrectAnswer({ correctAnswer: "true" })).toBeNull()
    expect(getJudgmentCorrectAnswer(null)).toBeNull()
  })
})

describe("getTextCorrectAnswers", () => {
  it("returns array from string correctAnswer", () => {
    expect(getTextCorrectAnswers({ correctAnswer: "yes" })).toEqual(["yes"])
  })
  it("returns array from string[] correctAnswer", () => {
    expect(getTextCorrectAnswers({ correctAnswer: ["yes", "Yes", "YES"] })).toEqual([
      "yes",
      "Yes",
      "YES",
    ])
  })
  it("returns empty array for missing or invalid", () => {
    expect(getTextCorrectAnswers({})).toEqual([])
    expect(getTextCorrectAnswers({ correctAnswer: 123 })).toEqual([])
    expect(getTextCorrectAnswers({ correctAnswer: [1, 2] })).toEqual([])
  })
})

describe("parseSavedAnswer", () => {
  it("parses object with answer field", () => {
    expect(parseSavedAnswer({ answer: "test" }, "text")).toEqual({ answer: "test" })
    expect(parseSavedAnswer({ answer: ["a", "b"] }, "multiple_choice")).toEqual({
      answer: ["a", "b"],
    })
  })
  it("returns empty answer for null/undefined input", () => {
    expect(parseSavedAnswer(null, "text")).toEqual({ answer: "" })
    expect(parseSavedAnswer(undefined, "text")).toEqual({ answer: "" })
  })
  it("coerces non-matching types to defaults", () => {
    expect(parseSavedAnswer("raw-string", "text")).toEqual({ answer: "raw-string" })
    expect(parseSavedAnswer(123, "text")).toEqual({ answer: "" })
    expect(parseSavedAnswer("not-boolean", "judgment")).toEqual({ answer: false })
  })
})

describe("extractAnswerValue", () => {
  it("extracts answer from object shape", () => {
    expect(extractAnswerValue({ answer: "test" })).toBe("test")
    expect(extractAnswerValue({ answer: ["a", "b"] })).toEqual(["a", "b"])
  })
  it("returns raw value for non-object shapes", () => {
    expect(extractAnswerValue("test")).toBe("test")
    expect(extractAnswerValue(["a", "b"])).toEqual(["a", "b"])
    expect(extractAnswerValue(null)).toBeNull()
  })
})

describe("normalizeText", () => {
  it("trims and lowercases", () => {
    expect(normalizeText("  Hello World  ")).toBe("hello world")
  })
  it("collapses internal whitespace", () => {
    expect(normalizeText("a   b\tc")).toBe("a b c")
  })
  it("returns empty string for empty input", () => {
    expect(normalizeText("")).toBe("")
    expect(normalizeText("   ")).toBe("")
  })
})

describe("isAutoGradable", () => {
  const choiceContent = {
    options: [{ id: "a", text: "A", isCorrect: true }],
  }
  const judgmentContent = { correctAnswer: true }

  it("returns true for choice types with correct options", () => {
    expect(isAutoGradable({ questionType: "single_choice", questionContent: choiceContent })).toBe(true)
    expect(isAutoGradable({ questionType: "multiple_choice", questionContent: choiceContent })).toBe(true)
  })
  it("returns false for choice types without correct options", () => {
    expect(isAutoGradable({ questionType: "single_choice", questionContent: {} })).toBe(false)
    expect(isAutoGradable({ questionType: "multiple_choice", questionContent: {} })).toBe(false)
  })
  it("returns true for judgment with boolean correctAnswer", () => {
    expect(isAutoGradable({ questionType: "judgment", questionContent: judgmentContent })).toBe(true)
  })
  it("returns false for judgment without correctAnswer", () => {
    expect(isAutoGradable({ questionType: "judgment", questionContent: {} })).toBe(false)
  })
  it("returns true for text type with correctAnswer", () => {
    expect(
      isAutoGradable({ questionType: "text", questionContent: { correctAnswer: "yes" } })
    ).toBe(true)
  })
  it("returns false for text type without correctAnswer", () => {
    expect(isAutoGradable({ questionType: "text", questionContent: {} })).toBe(false)
  })
  it("returns false for unknown types", () => {
    expect(isAutoGradable({ questionType: "essay", questionContent: {} })).toBe(false)
    expect(isAutoGradable({ questionType: "fill_blank", questionContent: {} })).toBe(false)
  })
})

describe("computeIsCorrect", () => {
  const singleChoiceContent = {
    options: [
      { id: "a", text: "A", isCorrect: true },
      { id: "b", text: "B" },
    ],
  }
  const multipleChoiceContent = {
    options: [
      { id: "a", text: "A", isCorrect: true },
      { id: "b", text: "B" },
      { id: "c", text: "C", isCorrect: true },
    ],
  }
  const judgmentContent = { correctAnswer: true }
  const textContent = { correctAnswer: ["yes", "Yes"] }

  it("single_choice: correct", () => {
    expect(
      computeIsCorrect({
        questionType: "single_choice",
        questionContent: singleChoiceContent,
        studentAnswer: "a",
      })
    ).toBe(true)
  })
  it("single_choice: incorrect", () => {
    expect(
      computeIsCorrect({
        questionType: "single_choice",
        questionContent: singleChoiceContent,
        studentAnswer: "b",
      })
    ).toBe(false)
  })
  it("multiple_choice: correct (all and only correct)", () => {
    expect(
      computeIsCorrect({
        questionType: "multiple_choice",
        questionContent: multipleChoiceContent,
        studentAnswer: ["a", "c"],
      })
    ).toBe(true)
  })
  it("multiple_choice: incorrect (missing one correct)", () => {
    expect(
      computeIsCorrect({
        questionType: "multiple_choice",
        questionContent: multipleChoiceContent,
        studentAnswer: ["a"],
      })
    ).toBe(false)
  })
  it("multiple_choice: incorrect (extra wrong option)", () => {
    expect(
      computeIsCorrect({
        questionType: "multiple_choice",
        questionContent: multipleChoiceContent,
        studentAnswer: ["a", "b", "c"],
      })
    ).toBe(false)
  })
  it("judgment: correct", () => {
    expect(
      computeIsCorrect({
        questionType: "judgment",
        questionContent: judgmentContent,
        studentAnswer: true,
      })
    ).toBe(true)
  })
  it("judgment: incorrect", () => {
    expect(
      computeIsCorrect({
        questionType: "judgment",
        questionContent: judgmentContent,
        studentAnswer: false,
      })
    ).toBe(false)
  })
  it("text: correct (case-insensitive)", () => {
    expect(
      computeIsCorrect({
        questionType: "text",
        questionContent: textContent,
        studentAnswer: "  YES  ",
      })
    ).toBe(true)
  })
  it("text: incorrect", () => {
    expect(
      computeIsCorrect({
        questionType: "text",
        questionContent: textContent,
        studentAnswer: "no",
      })
    ).toBe(false)
  })
  it("returns null for non-auto-gradable types", () => {
    expect(
      computeIsCorrect({
        questionType: "essay",
        questionContent: {},
        studentAnswer: "some text",
      })
    ).toBeNull()
  })
})

describe("getCorrectnessState", () => {
  it("returns 'ungraded' when score is null", () => {
    expect(getCorrectnessState({ score: null, maxScore: 10 })).toBe("ungraded")
  })
  it("returns 'correct' when score equals maxScore", () => {
    expect(getCorrectnessState({ score: 10, maxScore: 10 })).toBe("correct")
  })
  it("returns 'incorrect' when score is 0", () => {
    expect(getCorrectnessState({ score: 0, maxScore: 10 })).toBe("incorrect")
  })
  it("returns 'partial' when 0 < score < maxScore", () => {
    expect(getCorrectnessState({ score: 5, maxScore: 10 })).toBe("partial")
  })
})

describe("applyAutoGrades", () => {
  const baseAnswers: AutoGradableAnswer[] = [
    {
      id: "a1",
      questionId: "q1",
      questionType: "single_choice",
      questionContent: {
        options: [
          { id: "a", text: "A", isCorrect: true },
          { id: "b", text: "B" },
        ],
      },
      studentAnswer: "a",
      score: null,
      maxScore: 5,
      feedback: null,
      order: 0,
    },
    {
      id: "a2",
      questionId: "q2",
      questionType: "single_choice",
      questionContent: {
        options: [
          { id: "a", text: "A", isCorrect: true },
          { id: "b", text: "B" },
        ],
      },
      studentAnswer: "b",
      score: null,
      maxScore: 5,
      feedback: null,
      order: 1,
    },
    {
      id: "a3",
      questionId: "q3",
      questionType: "essay",
      questionContent: {},
      studentAnswer: "some text",
      score: null,
      maxScore: 10,
      feedback: null,
      order: 2,
    },
    {
      id: "a4",
      questionId: "q4",
      questionType: "single_choice",
      questionContent: {
        options: [{ id: "a", text: "A", isCorrect: true }],
      },
      studentAnswer: "a",
      score: 5,
      maxScore: 5,
      feedback: null,
      order: 3,
    },
  ]

  it("auto-grades correct answer to full score", () => {
    const result = applyAutoGrades(baseAnswers)
    expect(result[0].score).toBe(5)
  })
  it("auto-grades incorrect answer to 0", () => {
    const result = applyAutoGrades(baseAnswers)
    expect(result[1].score).toBe(0)
  })
  it("leaves non-auto-gradable answers with null score", () => {
    const result = applyAutoGrades(baseAnswers)
    expect(result[2].score).toBeNull()
  })
  it("does not overwrite already-graded answers", () => {
    const result = applyAutoGrades(baseAnswers)
    expect(result[3].score).toBe(5)
  })
})

describe("formatStudentAnswer", () => {
  it("formats string answer as-is", () => {
    expect(formatStudentAnswer("my answer")).toBe("my answer")
  })
  it("formats boolean answer", () => {
    expect(formatStudentAnswer(true)).toBe("True")
    expect(formatStudentAnswer(false)).toBe("False")
  })
  it("formats array answer as comma-separated", () => {
    expect(formatStudentAnswer(["a", "b", "c"])).toBe("a, b, c")
  })
  it("formats null answer as dash", () => {
    expect(formatStudentAnswer(null)).toBe("—")
    expect(formatStudentAnswer(undefined)).toBe("—")
  })
  it("formats object answer by extracting answer field", () => {
    expect(formatStudentAnswer({ answer: "test" })).toBe("test")
  })
})
