import { describe, expect, it } from 'vitest'
import {
  assignRolesWithPreferences,
  expandRolesToList,
  rolesListToDict,
  shuffle,
  type Rng,
} from '../logic/assignment'

// 可控 RNG：按预设序列返回。next() 越界后从头循环。
function seqRng(values: number[]): Rng {
  let i = 0
  return {
    next() {
      const v = values[i % values.length]
      i++
      return v
    },
  }
}

// 简单确定性 RNG：mulberry32，便于重现。
function mulberry32(seed: number): Rng {
  let a = seed
  return {
    next() {
      a |= 0
      a = (a + 0x6d2b79f5) | 0
      let t = a
      t = Math.imul(t ^ (t >>> 15), t | 1)
      t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296
    },
  }
}

describe('expandRolesToList', () => {
  it('expands a dict into a flat list', () => {
    const out = expandRolesToList({ 狼人: 2, 村民: 1 })
    // 顺序不严格保证，只检查 counts
    expect(out.sort()).toEqual(['村民', '狼人', '狼人'].sort())
  })

  it('passes a list through unchanged', () => {
    expect(expandRolesToList(['狼人', '村民'])).toEqual(['狼人', '村民'])
  })

  it('handles empty inputs', () => {
    expect(expandRolesToList({})).toEqual([])
    expect(expandRolesToList([])).toEqual([])
  })
})

describe('rolesListToDict', () => {
  it('counts roles', () => {
    expect(rolesListToDict(['狼人', '狼人', '村民'])).toEqual({ 狼人: 2, 村民: 1 })
  })
})

describe('shuffle', () => {
  it('does not lose elements', () => {
    const input = [1, 2, 3, 4, 5]
    const out = shuffle(input, mulberry32(42))
    expect(out.sort()).toEqual([1, 2, 3, 4, 5])
  })

  it('does not mutate the input', () => {
    const input = [1, 2, 3, 4, 5]
    shuffle(input, mulberry32(7))
    expect(input).toEqual([1, 2, 3, 4, 5])
  })

  it('is deterministic given a deterministic rng', () => {
    const a = shuffle([1, 2, 3, 4, 5, 6, 7, 8], mulberry32(123))
    const b = shuffle([1, 2, 3, 4, 5, 6, 7, 8], mulberry32(123))
    expect(a).toEqual(b)
  })
})

describe('assignRolesWithPreferences', () => {
  it('returns empty array for empty input', () => {
    expect(assignRolesWithPreferences([], {})).toEqual([])
  })

  it('returns a permutation of the input roles (no preferences)', () => {
    const roles = ['狼人', '狼人', '女巫', '猎人', '村民', '村民', '守卫', '骑士']
    const out = assignRolesWithPreferences(roles, {}, mulberry32(1))
    expect(out.length).toBe(roles.length)
    expect(out.sort()).toEqual([...roles].sort())
  })

  it('always assigns when player count equals 1', () => {
    expect(assignRolesWithPreferences(['狼人'], {}, mulberry32(0))).toEqual(['狼人'])
  })

  it('respects preferences with high probability over many trials', () => {
    // 玩家 1 想当狼人。池子 8 人 2 狼。无 preferences 时狼人命中率约 2/8 = 25%。
    // 4x 加权后命中率应该明显高于 25%。
    const roles = ['狼人', '狼人', '女巫', '猎人', '村民', '村民', '守卫', '骑士']
    const TRIALS = 2000
    let hits = 0
    for (let i = 0; i < TRIALS; i++) {
      const out = assignRolesWithPreferences(roles, { 1: ['狼人'] }, mulberry32(i + 1))
      if (out[0] === '狼人') hits++
    }
    const rate = hits / TRIALS
    // 25% baseline → 期望 > 40%（实测约 55-60%）。给个比较宽的下界。
    expect(rate).toBeGreaterThan(0.4)
  })

  it('without preferences, role distribution is roughly uniform', () => {
    const roles = ['狼人', '狼人', '村民', '村民', '村民', '村民', '村民', '村民']
    const TRIALS = 2000
    let p1Wolf = 0
    let p4Wolf = 0
    for (let i = 0; i < TRIALS; i++) {
      const out = assignRolesWithPreferences(roles, {}, mulberry32(i + 100))
      if (out[0] === '狼人') p1Wolf++
      if (out[3] === '狼人') p4Wolf++
    }
    // 每人是狼概率约 25%；4% 容忍区间
    expect(Math.abs(p1Wolf / TRIALS - 0.25)).toBeLessThan(0.05)
    expect(Math.abs(p4Wolf / TRIALS - 0.25)).toBeLessThan(0.05)
  })

  it('preserves role counts across all output positions', () => {
    const roles = ['狼人', '狼人', '狼人', '守卫', '守卫', '女巫', '猎人', '骑士', '白狼王', '村民']
    for (let seed = 0; seed < 20; seed++) {
      const out = assignRolesWithPreferences(roles, { 1: ['狼人'], 5: ['女巫'] }, mulberry32(seed))
      expect(out.length).toBe(roles.length)
      expect(out.sort()).toEqual([...roles].sort())
    }
  })

  it('uses sequence rng deterministically', () => {
    // 玩家顺序 shuffle 用 rng，加权抽取也用 rng — 不依赖具体内部顺序，但同 rng 两次必相同。
    const roles = ['狼人', '女巫', '村民', '猎人']
    const a = assignRolesWithPreferences(roles, {}, seqRng([0.1, 0.5, 0.9, 0.2, 0.7, 0.3, 0.8]))
    const b = assignRolesWithPreferences(roles, {}, seqRng([0.1, 0.5, 0.9, 0.2, 0.7, 0.3, 0.8]))
    expect(a).toEqual(b)
  })
})
