import { describe, it, expect } from 'vitest'
import { calculateCost, getModelCosts, getShortModelName } from '../src/models.js'

// Lock down the post-hoist refactor: every model name a real user has
// emitted in the last year should resolve to the same display name and
// the same costs as before. If this list grows or shrinks, the refactor
// is fine — it's the per-name resolution that must stay stable.
const KNOWN_NAMES = [
  'claude-opus-4-7',
  'claude-opus-4-6',
  'claude-opus-4-5',
  'claude-sonnet-4-6',
  'claude-sonnet-4-5',
  'claude-haiku-4-5',
  'claude-3-5-sonnet',
  'claude-3-5-haiku',
  'claude-opus-4-7-20250101',
  'claude-sonnet-4-6-20250929',
  'anthropic/claude-opus-4-7',
  'anthropic--claude-4.6-opus',
  'anthropic--claude-4.6-sonnet',
  'claude-4.6-sonnet',
  'gpt-5',
  'gpt-5-mini',
  'gpt-5-nano',
  'gpt-5-pro',
  'gpt-5.1',
  'gpt-5.1-codex',
  'gpt-5.1-codex-mini',
  'gpt-5.2',
  'gpt-5.2-low',
  'gpt-5.3-codex',
  'gpt-5.4',
  'gpt-5.4-mini',
  'gpt-4o',
  'gpt-4o-mini',
  'gpt-4.1',
  'gpt-4.1-mini',
  'gpt-4.1-nano',
  'gemini-2.5-pro',
  'gemini-2.5-flash',
  'gemini-3.1-pro-preview',
  'gemini-3-flash-preview',
  'gemini-3.1-pro',
  'gemini-3-flash',
  'cursor-auto',
  'cursor-agent-auto',
  'copilot-auto',
  'copilot-openai-auto',
  'kiro-auto',
  'cline-auto',
  'qwen-auto',
  'kimi-auto',
  'kimi-for-coding',
  'kimi-k2-thinking-turbo',
  'kimi-k2.6',
  'o3',
  'o4-mini',
  'deepseek-coder',
  'deepseek-coder-max',
  'deepseek-r1',
  'MiniMax-M2.7',
  'MiniMax-M2.7-highspeed',
]

describe('post-hoist resolution stability', () => {
  it('every known model resolves to a non-empty short name', () => {
    for (const name of KNOWN_NAMES) {
      const short = getShortModelName(name)
      expect(short, `short name for ${name}`).toBeTruthy()
      expect(typeof short, `short name for ${name}`).toBe('string')
    }
  })

  it('gpt-5-mini does NOT collide with gpt-5 (longest-prefix wins)', () => {
    expect(getShortModelName('gpt-5-mini')).toBe('GPT-5 Mini')
    expect(getShortModelName('gpt-5')).toBe('GPT-5')
    expect(getShortModelName('gpt-5-nano')).toBe('GPT-5 Nano')
    expect(getShortModelName('gpt-5-pro')).toBe('GPT-5 Pro')
  })

  it('gpt-5.1-codex-mini does NOT collapse to gpt-5.1-codex or gpt-5', () => {
    expect(getShortModelName('gpt-5.1-codex-mini')).toBe('GPT-5.1 Codex Mini')
    expect(getShortModelName('gpt-5.1-codex')).toBe('GPT-5.1 Codex')
    expect(getShortModelName('gpt-5.1')).toBe('GPT-5.1')
  })

  it('claude-haiku-4-5 does NOT collapse to claude-haiku-4 or claude-3-5-haiku', () => {
    expect(getShortModelName('claude-haiku-4-5')).toBe('Haiku 4.5')
    expect(getShortModelName('claude-3-5-haiku')).toBe('Haiku 3.5')
  })

  it('kimi managed aliases resolve to priced Kimi models', () => {
    expect(getShortModelName('kimi-auto')).toBe('Kimi (auto)')
    expect(getShortModelName('kimi-for-coding')).toBe('Kimi K2 Thinking')
    expect(getShortModelName('kimi-k2-thinking-turbo')).toBe('Kimi K2 Thinking Turbo')
    expect(getShortModelName('kimi-k2.6')).toBe('Kimi K2.6')
    expect(getModelCosts('kimi-auto')?.inputCostPerToken).toBeGreaterThan(0)
  })

  it('getModelCosts returns positive token costs for every known name', () => {
    for (const name of KNOWN_NAMES) {
      const c = getModelCosts(name)
      expect(c, `costs for ${name}`).not.toBeNull()
      expect(c!.inputCostPerToken).toBeGreaterThan(0)
      expect(c!.outputCostPerToken).toBeGreaterThan(0)
    }
  })

  it('calculateCost is stable for a typical Sonnet 4.6 turn', () => {
    // 1k input, 2k output, 50k cache read — common Claude Code shape.
    const cost = calculateCost('claude-sonnet-4-6', 1000, 2000, 0, 50_000, 0)
    expect(cost).toBeGreaterThan(0)
    expect(Number.isFinite(cost)).toBe(true)
  })

  it('calculateCost clamps NaN/negative inputs to 0', () => {
    const c1 = calculateCost('claude-sonnet-4-6', NaN, 1000, 0, 0, 0)
    const c2 = calculateCost('claude-sonnet-4-6', 0, 1000, 0, 0, 0)
    expect(c1).toBe(c2)
    const c3 = calculateCost('claude-sonnet-4-6', -1000, 1000, 0, 0, 0)
    expect(c3).toBe(c2)
  })

  it('repeated calls return the same cost (memoized sort cache is consistent)', () => {
    const a = getModelCosts('gpt-5-mini')
    const b = getModelCosts('gpt-5-mini')
    const c = getModelCosts('gpt-5-mini')
    expect(a).toEqual(b)
    expect(b).toEqual(c)
  })
})
