import { describe, it, expect } from "vitest"
import {
  calculateNewInterval,
  calculateNewMastery,
  deriveStatus,
  calculateNextReviewAt,
  calculateNewCorrectStreak,
  calculateSm2Result,
  REVIEW_INTERVALS,
  INTERVAL_MULTIPLIERS,
  MAX_MASTERY_LEVEL,
  MIN_MASTERY_LEVEL,
  MASTERED_REQUIRED_STREAK,
  MASTERED_REQUIRED_MASTERY,
} from "./sm2-algorithm"
import type { ErrorBookReviewResult } from "./schema"

describe("SM-2 间隔重复算法", () => {
  describe("REVIEW_INTERVALS 常量", () => {
    it("应该包含 4 种评级的映射", () => {
      expect(REVIEW_INTERVALS.again).toBeDefined()
      expect(REVIEW_INTERVALS.hard).toBeDefined()
      expect(REVIEW_INTERVALS.good).toBeDefined()
      expect(REVIEW_INTERVALS.easy).toBeDefined()
    })

    it("again 应该重置连续答对", () => {
      expect(REVIEW_INTERVALS.again.streakDelta).toBeLessThan(0)
    })

    it("good 和 easy 应该增加连续答对", () => {
      expect(REVIEW_INTERVALS.good.streakDelta).toBe(1)
      expect(REVIEW_INTERVALS.easy.streakDelta).toBe(1)
    })
  })

  describe("INTERVAL_MULTIPLIERS 常量", () => {
    it("easy 的倍率应该最高", () => {
      expect(INTERVAL_MULTIPLIERS.easy).toBeGreaterThan(INTERVAL_MULTIPLIERS.good)
      expect(INTERVAL_MULTIPLIERS.good).toBeGreaterThan(INTERVAL_MULTIPLIERS.hard)
    })

    it("所有倍率应该 >= 1", () => {
      for (const multiplier of Object.values(INTERVAL_MULTIPLIERS)) {
        expect(multiplier).toBeGreaterThanOrEqual(1)
      }
    })
  })

  describe("calculateNewInterval", () => {
    it("again 总是返回 1 天", () => {
      expect(calculateNewInterval(10, "again", 5)).toBe(1)
      expect(calculateNewInterval(100, "again", 10)).toBe(1)
    })

    it("首次复习（reviewCount=0）使用基础间隔", () => {
      expect(calculateNewInterval(0, "hard", 0)).toBe(REVIEW_INTERVALS.hard.interval)
      expect(calculateNewInterval(0, "good", 0)).toBe(REVIEW_INTERVALS.good.interval)
      expect(calculateNewInterval(0, "easy", 0)).toBe(REVIEW_INTERVALS.easy.interval)
    })

    it("good 评级应该按 ×1.5 增长", () => {
      const result = calculateNewInterval(10, "good", 1)
      expect(result).toBe(Math.max(REVIEW_INTERVALS.good.interval, Math.round(10 * 1.5)))
      expect(result).toBe(15)
    })

    it("easy 评级应该按 ×2 增长", () => {
      const result = calculateNewInterval(10, "easy", 1)
      expect(result).toBe(Math.max(REVIEW_INTERVALS.easy.interval, Math.round(10 * 2)))
      expect(result).toBe(20)
    })

    it("hard 评级应该按 ×1.2 增长", () => {
      const result = calculateNewInterval(10, "hard", 1)
      expect(result).toBe(Math.max(REVIEW_INTERVALS.hard.interval, Math.round(10 * 1.2)))
      expect(result).toBe(12)
    })

    it("新间隔不应该小于基础间隔", () => {
      expect(calculateNewInterval(1, "good", 1)).toBeGreaterThanOrEqual(REVIEW_INTERVALS.good.interval)
      expect(calculateNewInterval(1, "easy", 1)).toBeGreaterThanOrEqual(REVIEW_INTERVALS.easy.interval)
    })
  })

  describe("calculateNewMastery", () => {
    it("again 应该降低掌握度", () => {
      expect(calculateNewMastery(3, "again", 0)).toBe(2)
      expect(calculateNewMastery(5, "again", 0)).toBe(4)
    })

    it("hard 应该保持掌握度不变", () => {
      expect(calculateNewMastery(3, "hard", 0)).toBe(3)
      expect(calculateNewMastery(0, "hard", 0)).toBe(0)
    })

    it("good 应该增加掌握度", () => {
      expect(calculateNewMastery(2, "good", 0)).toBe(3)
      expect(calculateNewMastery(4, "good", 0)).toBe(5)
    })

    it("easy 应该大幅增加掌握度", () => {
      expect(calculateNewMastery(1, "easy", 0)).toBe(3)
      expect(calculateNewMastery(3, "easy", 0)).toBe(5)
    })

    it("掌握度不应该超过上限", () => {
      expect(calculateNewMastery(5, "easy", 0)).toBe(MAX_MASTERY_LEVEL)
      expect(calculateNewMastery(5, "good", 0)).toBe(MAX_MASTERY_LEVEL)
    })

    it("掌握度不应该低于下限", () => {
      expect(calculateNewMastery(0, "again", 0)).toBe(MIN_MASTERY_LEVEL)
      expect(calculateNewMastery(0, "hard", 0)).toBe(MIN_MASTERY_LEVEL)
    })

    it("连续 3 次答对应该直接达到已掌握", () => {
      expect(calculateNewMastery(2, "good", 3)).toBe(MAX_MASTERY_LEVEL)
      expect(calculateNewMastery(1, "easy", 3)).toBe(MAX_MASTERY_LEVEL)
    })
  })

  describe("deriveStatus", () => {
    it("掌握度 0 应该返回 new", () => {
      expect(deriveStatus(0, 0)).toBe("new")
    })

    it("掌握度 1-4 应该返回 learning", () => {
      expect(deriveStatus(1, 0)).toBe("learning")
      expect(deriveStatus(2, 0)).toBe("learning")
      expect(deriveStatus(3, 0)).toBe("learning")
      expect(deriveStatus(4, 0)).toBe("learning")
    })

    it("掌握度 5 应该返回 mastered", () => {
      expect(deriveStatus(5, 0)).toBe("mastered")
    })

    it("连续答对 3 次应该返回 mastered", () => {
      expect(deriveStatus(1, 3)).toBe("mastered")
      expect(deriveStatus(2, 3)).toBe("mastered")
    })

    it("连续答对 2 次且掌握度 < 5 应该返回 learning", () => {
      expect(deriveStatus(2, 2)).toBe("learning")
    })
  })

  describe("calculateNextReviewAt", () => {
    it("应该返回正确的日期（+intervalDays 天）", () => {
      const now = new Date("2026-01-15T10:00:00Z")
      const result = calculateNextReviewAt(7, now)
      expect(result.getDate()).toBe(now.getDate() + 7)
    })

    it("应该设置时间为早上 9 点", () => {
      const now = new Date("2026-01-15T22:00:00Z")
      const result = calculateNextReviewAt(1, now)
      expect(result.getHours()).toBe(9)
      expect(result.getMinutes()).toBe(0)
      expect(result.getSeconds()).toBe(0)
    })

    it("间隔为 0 天时应该返回当天", () => {
      const now = new Date("2026-01-15T10:00:00Z")
      const result = calculateNextReviewAt(0, now)
      expect(result.getDate()).toBe(now.getDate())
    })
  })

  describe("calculateNewCorrectStreak", () => {
    it("again 应该重置连续答对为 0", () => {
      expect(calculateNewCorrectStreak(5, "again")).toBe(0)
      expect(calculateNewCorrectStreak(0, "again")).toBe(0)
    })

    it("hard 应该保持连续答对不变", () => {
      expect(calculateNewCorrectStreak(3, "hard")).toBe(3)
      expect(calculateNewCorrectStreak(0, "hard")).toBe(0)
    })

    it("good 应该增加连续答对", () => {
      expect(calculateNewCorrectStreak(2, "good")).toBe(3)
      expect(calculateNewCorrectStreak(0, "good")).toBe(1)
    })

    it("easy 应该增加连续答对", () => {
      expect(calculateNewCorrectStreak(2, "easy")).toBe(3)
      expect(calculateNewCorrectStreak(0, "easy")).toBe(1)
    })
  })

  describe("calculateSm2Result", () => {
    it("应该返回完整的计算结果", () => {
      const result = calculateSm2Result(
        {
          currentInterval: 4,
          currentMastery: 2,
          currentStreak: 1,
          reviewCount: 2,
        },
        "good"
      )

      expect(result).toHaveProperty("newInterval")
      expect(result).toHaveProperty("newMasteryLevel")
      expect(result).toHaveProperty("newCorrectStreak")
      expect(result).toHaveProperty("newStatus")
      expect(result).toHaveProperty("nextReviewAt")
    })

    it("good 评级应该正确计算所有字段", () => {
      const result = calculateSm2Result(
        {
          currentInterval: 4,
          currentMastery: 2,
          currentStreak: 1,
          reviewCount: 2,
        },
        "good"
      )

      expect(result.newInterval).toBe(6) // max(4, round(4 * 1.5)) = 6
      expect(result.newMasteryLevel).toBe(3) // 2 + 1 = 3
      expect(result.newCorrectStreak).toBe(2) // 1 + 1 = 2
      expect(result.newStatus).toBe("learning") // mastery 3, streak 2
    })

    it("连续 3 次 good 应该达到 mastered", () => {
      const result = calculateSm2Result(
        {
          currentInterval: 4,
          currentMastery: 2,
          currentStreak: 2,
          reviewCount: 2,
        },
        "good"
      )

      expect(result.newCorrectStreak).toBe(3)
      expect(result.newMasteryLevel).toBe(MAX_MASTERY_LEVEL) // 连续 3 次直接到 5
      expect(result.newStatus).toBe("mastered")
    })

    it("again 应该重置所有进度", () => {
      const result = calculateSm2Result(
        {
          currentInterval: 20,
          currentMastery: 4,
          currentStreak: 2,
          reviewCount: 5,
        },
        "again"
      )

      expect(result.newInterval).toBe(1)
      expect(result.newMasteryLevel).toBe(3) // 4 - 1 = 3
      expect(result.newCorrectStreak).toBe(0)
      expect(result.newStatus).toBe("learning") // mastery 3
    })

    it("所有评级都应该产生有效结果", () => {
      const ratings: ErrorBookReviewResult[] = ["again", "hard", "good", "easy"]
      for (const rating of ratings) {
        const result = calculateSm2Result(
          {
            currentInterval: 4,
            currentMastery: 2,
            currentStreak: 1,
            reviewCount: 2,
          },
          rating
        )

        expect(result.newInterval).toBeGreaterThan(0)
        expect(result.newMasteryLevel).toBeGreaterThanOrEqual(MIN_MASTERY_LEVEL)
        expect(result.newMasteryLevel).toBeLessThanOrEqual(MAX_MASTERY_LEVEL)
        expect(result.newCorrectStreak).toBeGreaterThanOrEqual(0)
        expect(["new", "learning", "mastered"]).toContain(result.newStatus)
        expect(result.nextReviewAt).toBeInstanceOf(Date)
      }
    })
  })

  describe("常量一致性", () => {
    it("MASTERED_REQUIRED_STREAK 应该是 3", () => {
      expect(MASTERED_REQUIRED_STREAK).toBe(3)
    })

    it("MASTERED_REQUIRED_MASTERY 应该是 5", () => {
      expect(MASTERED_REQUIRED_MASTERY).toBe(5)
    })

    it("MAX_MASTERY_LEVEL 应该是 5", () => {
      expect(MAX_MASTERY_LEVEL).toBe(5)
    })

    it("MIN_MASTERY_LEVEL 应该是 0", () => {
      expect(MIN_MASTERY_LEVEL).toBe(0)
    })
  })
})
