const { describe, test, expect, beforeEach, afterEach } = require('bun:test')

const OPENROUTER_STATIC_FALLBACK_MODELS = [
  'anthropic/claude-3.5-sonnet',
  'openai/gpt-4o-mini',
  'mistralai/mistral-large-latest',
  'google/gemini-flash-1.5',
]

const DEFAULT_TOKEN_RATES = {
  usd: 250,
  gbp: 200,
  eur: 250,
}

const PLAN_TOKEN_LIMITS = {
  hobby: 50_000,
  starter: 100_000,
  professional: 5_000_000,
  enterprise: 20_000_000,
}

const PLAN_APP_LIMITS = {
  hobby: 3,
  starter: 10,
  professional: 20,
  enterprise: Infinity,
}

const EXTERNAL_TEST_LIMITS = {
  hobby: 3,
  starter: 50,
  professional: Infinity,
  enterprise: Infinity,
}

const PAID_PLANS = new Set(['starter', 'professional', 'enterprise'])

function normalizePlanSelection(plan) {
  const validPlans = ['hobby', 'starter', 'professional', 'enterprise']
  return validPlans.includes(plan) ? plan : 'hobby'
}

function isPaidPlan(plan) {
  return PAID_PLANS.has(plan)
}

function getPlanTokenLimit(plan) {
  return PLAN_TOKEN_LIMITS[normalizePlanSelection(plan)] || PLAN_TOKEN_LIMITS.hobby
}

function getPlanAppLimit(plan) {
  return PLAN_APP_LIMITS[normalizePlanSelection(plan)] || PLAN_APP_LIMITS.hobby
}

function getExternalTestLimit(plan) {
  return EXTERNAL_TEST_LIMITS[normalizePlanSelection(plan)] || EXTERNAL_TEST_LIMITS.hobby
}

function calculateTokenCost(tokens, currency) {
  const rate = DEFAULT_TOKEN_RATES[currency] || DEFAULT_TOKEN_RATES.usd
  return (tokens / 1_000_000) * rate
}

function parseMemoryValue(raw) {
  if (raw === undefined || raw === null) return 0
  const str = String(raw).trim()
  const match = str.match(/^([0-9.]+)\s*([kKmMgGtT]i?)?(?:[bB])?$/)
  if (!match) return Number(str) || 0
  const value = parseFloat(match[1])
  const unit = (match[2] || '').toLowerCase()
  const KIB = 1024
  const MIB = KIB * 1024
  const GIB = MIB * 1024
  const TIB = GIB * 1024
  const multipliers = { '': 1, k: KIB, ki: KIB, m: MIB, mi: MIB, g: GIB, gi: GIB, t: TIB, ti: TIB }
  if (!Number.isFinite(value) || value <= 0) return 0
  return Math.round(value * (multipliers[unit] || 1))
}

function resolvePositiveInt(raw, fallback) {
  const parsed = Number(raw)
  if (!Number.isFinite(parsed) || parsed <= 0) return fallback
  return Math.round(parsed)
}

function resolveIntervalMs(raw, fallback, min) {
  const parsed = Number(raw)
  if (!Number.isFinite(parsed) || parsed <= 0) return fallback
  return Math.max(parsed, min)
}

describe('Model Routing', () => {
  describe('Plan Selection and Validation', () => {
    test('normalizes valid plans', () => {
      expect(normalizePlanSelection('hobby')).toBe('hobby')
      expect(normalizePlanSelection('starter')).toBe('starter')
      expect(normalizePlanSelection('professional')).toBe('professional')
      expect(normalizePlanSelection('enterprise')).toBe('enterprise')
    })

    test('defaults invalid plans to hobby', () => {
      expect(normalizePlanSelection('invalid')).toBe('hobby')
      expect(normalizePlanSelection('')).toBe('hobby')
      expect(normalizePlanSelection(null)).toBe('hobby')
      expect(normalizePlanSelection(undefined)).toBe('hobby')
      expect(normalizePlanSelection('free')).toBe('hobby')
    })

    test('identifies paid plans correctly', () => {
      expect(isPaidPlan('hobby')).toBe(false)
      expect(isPaidPlan('starter')).toBe(true)
      expect(isPaidPlan('professional')).toBe(true)
      expect(isPaidPlan('enterprise')).toBe(true)
    })

    test('returns correct token limits by plan', () => {
      expect(getPlanTokenLimit('hobby')).toBe(50_000)
      expect(getPlanTokenLimit('starter')).toBe(100_000)
      expect(getPlanTokenLimit('professional')).toBe(5_000_000)
      expect(getPlanTokenLimit('enterprise')).toBe(20_000_000)
    })

    test('returns correct app limits by plan', () => {
      expect(getPlanAppLimit('hobby')).toBe(3)
      expect(getPlanAppLimit('starter')).toBe(10)
      expect(getPlanAppLimit('professional')).toBe(20)
      expect(getPlanAppLimit('enterprise')).toBe(Infinity)
    })

    test('returns correct external test limits by plan', () => {
      expect(getExternalTestLimit('hobby')).toBe(3)
      expect(getExternalTestLimit('starter')).toBe(50)
      expect(getExternalTestLimit('professional')).toBe(Infinity)
      expect(getExternalTestLimit('enterprise')).toBe(Infinity)
    })
  })

  describe('Provider Fallback Models', () => {
    test('has fallback models available', () => {
      expect(OPENROUTER_STATIC_FALLBACK_MODELS.length).toBeGreaterThan(0)
    })

    test('includes major providers', () => {
      expect(OPENROUTER_STATIC_FALLBACK_MODELS.some(m => m.includes('anthropic'))).toBe(true)
      expect(OPENROUTER_STATIC_FALLBACK_MODELS.some(m => m.includes('openai'))).toBe(true)
      expect(OPENROUTER_STATIC_FALLBACK_MODELS.some(m => m.includes('mistralai'))).toBe(true)
      expect(OPENROUTER_STATIC_FALLBACK_MODELS.some(m => m.includes('google'))).toBe(true)
    })

    test('fallback models are properly formatted', () => {
      OPENROUTER_STATIC_FALLBACK_MODELS.forEach(model => {
        expect(model).toMatch(/^[a-z0-9-]+\/[a-z0-9.-]+$/i)
      })
    })

    test('gets next fallback model', () => {
      const usedModels = new Set(['anthropic/claude-3.5-sonnet'])
      const nextModel = OPENROUTER_STATIC_FALLBACK_MODELS.find(m => !usedModels.has(m))
      
      expect(nextModel).toBeDefined()
      expect(nextModel).not.toBe('anthropic/claude-3.5-sonnet')
    })

    test('returns undefined when all fallbacks exhausted', () => {
      const usedModels = new Set(OPENROUTER_STATIC_FALLBACK_MODELS)
      const nextModel = OPENROUTER_STATIC_FALLBACK_MODELS.find(m => !usedModels.has(m))
      
      expect(nextModel).toBeUndefined()
    })
  })

  describe('Model Selection Logic', () => {
    const modelTiers = {
      free: ['openai/gpt-3.5-turbo', 'google/gemini-flash-1.5'],
      standard: ['openai/gpt-4o-mini', 'anthropic/claude-3-haiku'],
      premium: ['openai/gpt-4o', 'anthropic/claude-3.5-sonnet'],
      enterprise: ['anthropic/claude-3-opus', 'openai/gpt-4-turbo']
    }

    function getModelsForPlan(plan) {
      const normalized = normalizePlanSelection(plan)
      if (normalized === 'enterprise') {
        return [...modelTiers.free, ...modelTiers.standard, ...modelTiers.premium, ...modelTiers.enterprise]
      }
      if (normalized === 'professional') {
        return [...modelTiers.free, ...modelTiers.standard, ...modelTiers.premium]
      }
      if (normalized === 'starter') {
        return [...modelTiers.free, ...modelTiers.standard]
      }
      return [...modelTiers.free]
    }

    test('hobby plan gets free models only', () => {
      const models = getModelsForPlan('hobby')
      expect(models).toEqual(modelTiers.free)
    })

    test('starter plan gets free and standard models', () => {
      const models = getModelsForPlan('starter')
      expect(models).toContain('openai/gpt-3.5-turbo')
      expect(models).toContain('openai/gpt-4o-mini')
      expect(models).not.toContain('openai/gpt-4o')
    })

    test('professional plan gets up to premium models', () => {
      const models = getModelsForPlan('professional')
      expect(models).toContain('openai/gpt-4o')
      expect(models).not.toContain('anthropic/claude-3-opus')
    })

    test('enterprise plan gets all models', () => {
      const models = getModelsForPlan('enterprise')
      expect(models).toContain('anthropic/claude-3-opus')
    })
  })

  describe('Token Rate Calculations', () => {
    test('calculates USD token cost', () => {
      const cost = calculateTokenCost(1_000_000, 'usd')
      expect(cost).toBe(250)
    })

    test('calculates GBP token cost', () => {
      const cost = calculateTokenCost(1_000_000, 'gbp')
      expect(cost).toBe(200)
    })

    test('calculates EUR token cost', () => {
      const cost = calculateTokenCost(1_000_000, 'eur')
      expect(cost).toBe(250)
    })

    test('calculates partial token cost', () => {
      const cost = calculateTokenCost(500_000, 'usd')
      expect(cost).toBe(125)
    })

    test('defaults to USD for unknown currency', () => {
      const cost = calculateTokenCost(1_000_000, 'unknown')
      expect(cost).toBe(250)
    })

    test('calculates zero cost for zero tokens', () => {
      const cost = calculateTokenCost(0, 'usd')
      expect(cost).toBe(0)
    })
  })

  describe('Memory and Resource Parsing', () => {
    test('parses bytes value', () => {
      expect(parseMemoryValue('1024')).toBe(1024)
    })

    test('parses kilobytes', () => {
      expect(parseMemoryValue('1K')).toBe(1024)
      expect(parseMemoryValue('1KB')).toBe(1024)
      expect(parseMemoryValue('1KiB')).toBe(1024)
    })

    test('parses megabytes', () => {
      expect(parseMemoryValue('1M')).toBe(1024 * 1024)
      expect(parseMemoryValue('512MB')).toBe(512 * 1024 * 1024)
    })

    test('parses gigabytes', () => {
      expect(parseMemoryValue('1G')).toBe(1024 * 1024 * 1024)
      expect(parseMemoryValue('2GB')).toBe(2 * 1024 * 1024 * 1024)
    })

    test('handles null/undefined', () => {
      expect(parseMemoryValue(null)).toBe(0)
      expect(parseMemoryValue(undefined)).toBe(0)
    })

    test('handles invalid input', () => {
      expect(parseMemoryValue('invalid')).toBe(0)
      expect(parseMemoryValue('')).toBe(0)
    })

    test('resolvePositiveInt returns fallback for invalid', () => {
      expect(resolvePositiveInt('invalid', 10)).toBe(10)
      expect(resolvePositiveInt(null, 10)).toBe(10)
      expect(resolvePositiveInt(-5, 10)).toBe(10)
    })

    test('resolvePositiveInt returns parsed value', () => {
      expect(resolvePositiveInt('50', 10)).toBe(50)
      expect(resolvePositiveInt(100, 10)).toBe(100)
    })

    test('resolveIntervalMs enforces minimum', () => {
      expect(resolveIntervalMs('100', 1000, 500)).toBe(500)
      expect(resolveIntervalMs('2000', 1000, 500)).toBe(2000)
    })
  })

  describe('Rate Limiting', () => {
    const rateLimiter = {
      requests: new Map(),
      windowMs: 60000,
      maxRequests: 10,
      
      check(key) {
        const now = Date.now()
        let record = this.requests.get(key)
        
        if (!record || now - record.windowStart > this.windowMs) {
          record = { count: 0, windowStart: now }
          this.requests.set(key, record)
        }
        
        record.count++
        
        return {
          allowed: record.count <= this.maxRequests,
          remaining: Math.max(0, this.maxRequests - record.count),
          resetAt: record.windowStart + this.windowMs
        }
      },
      
      reset() {
        this.requests.clear()
      }
    }

    beforeEach(() => {
      rateLimiter.reset()
    })

    test('allows first request', () => {
      const result = rateLimiter.check('user-1')
      expect(result.allowed).toBe(true)
      expect(result.remaining).toBe(9)
    })

    test('tracks multiple requests', () => {
      for (let i = 0; i < 5; i++) {
        rateLimiter.check('user-2')
      }
      const result = rateLimiter.check('user-2')
      
      expect(result.allowed).toBe(true)
      expect(result.remaining).toBe(4)
    })

    test('blocks after limit reached', () => {
      for (let i = 0; i < 10; i++) {
        rateLimiter.check('user-3')
      }
      const result = rateLimiter.check('user-3')
      
      expect(result.allowed).toBe(false)
      expect(result.remaining).toBe(0)
    })

    test('separate limits per key', () => {
      for (let i = 0; i < 10; i++) {
        rateLimiter.check('user-4a')
      }
      
      const resultA = rateLimiter.check('user-4a')
      const resultB = rateLimiter.check('user-4b')
      
      expect(resultA.allowed).toBe(false)
      expect(resultB.allowed).toBe(true)
    })
  })

  describe('Provider Health Checks', () => {
    const providerHealth = {
      openrouter: { status: 'healthy', latency: 150, lastCheck: Date.now() },
      mistral: { status: 'healthy', latency: 200, lastCheck: Date.now() },
      ollama: { status: 'degraded', latency: 500, lastCheck: Date.now() },
      google: { status: 'healthy', latency: 180, lastCheck: Date.now() }
    }

    function isProviderHealthy(provider) {
      const health = providerHealth[provider]
      if (!health) return false
      return health.status === 'healthy'
    }

    function getProviderLatency(provider) {
      return providerHealth[provider]?.latency || Infinity
    }

    function getHealthyProviders() {
      return Object.entries(providerHealth)
        .filter(([_, health]) => health.status === 'healthy')
        .map(([name]) => name)
    }

    test('checks OpenRouter health', () => {
      expect(isProviderHealthy('openrouter')).toBe(true)
    })

    test('detects degraded provider', () => {
      expect(isProviderHealthy('ollama')).toBe(false)
    })

    test('handles unknown provider', () => {
      expect(isProviderHealthy('unknown')).toBe(false)
    })

    test('gets provider latency', () => {
      expect(getProviderLatency('openrouter')).toBe(150)
      expect(getProviderLatency('unknown')).toBe(Infinity)
    })

    test('lists healthy providers', () => {
      const healthy = getHealthyProviders()
      expect(healthy).toContain('openrouter')
      expect(healthy).toContain('mistral')
      expect(healthy).toContain('google')
      expect(healthy).not.toContain('ollama')
    })
  })

  describe('Request Priority Queue', () => {
    const priorityQueue = {
      queue: [],
      
      add(request) {
        this.queue.push({
          ...request,
          addedAt: Date.now()
        })
        this.queue.sort((a, b) => a.priority - b.priority)
      },
      
      next() {
        return this.queue.shift()
      },
      
      length() {
        return this.queue.length
      },
      
      clear() {
        this.queue = []
      }
    }

    beforeEach(() => {
      priorityQueue.clear()
    })

    test('adds requests to queue', () => {
      priorityQueue.add({ id: 'req-1', priority: 5 })
      expect(priorityQueue.length()).toBe(1)
    })

    test('orders by priority', () => {
      priorityQueue.add({ id: 'req-1', priority: 5 })
      priorityQueue.add({ id: 'req-2', priority: 1 })
      priorityQueue.add({ id: 'req-3', priority: 3 })
      
      expect(priorityQueue.next().id).toBe('req-2')
      expect(priorityQueue.next().id).toBe('req-3')
      expect(priorityQueue.next().id).toBe('req-1')
    })

    test('processes in order', () => {
      priorityQueue.add({ id: 'first', priority: 1 })
      priorityQueue.add({ id: 'second', priority: 2 })
      
      expect(priorityQueue.next().id).toBe('first')
      expect(priorityQueue.next().id).toBe('second')
      expect(priorityQueue.length()).toBe(0)
    })
  })
})

describe('OpenRouter Configuration', () => {
  test('default API URL is correct', () => {
    const DEFAULT_OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'
    expect(DEFAULT_OPENROUTER_API_URL).toContain('openrouter.ai')
    expect(DEFAULT_OPENROUTER_API_URL).toContain('chat/completions')
  })

  test('default model is set', () => {
    const OPENROUTER_DEFAULT_MODEL = 'openai/gpt-4o-mini'
    expect(OPENROUTER_DEFAULT_MODEL).toMatch(/^[a-z0-9-]+\/[a-z0-9.-]+$/i)
  })

  test('error detail limit is reasonable', () => {
    const OPENROUTER_ERROR_DETAIL_LIMIT = 400
    expect(OPENROUTER_ERROR_DETAIL_LIMIT).toBeGreaterThan(0)
    expect(OPENROUTER_ERROR_DETAIL_LIMIT).toBeLessThan(1000)
  })
})

describe('Mistral Configuration', () => {
  test('default API URL is correct', () => {
    const DEFAULT_MISTRAL_API_URL = 'https://api.mistral.ai/v1/chat/completions'
    expect(DEFAULT_MISTRAL_API_URL).toContain('mistral.ai')
    expect(DEFAULT_MISTRAL_API_URL).toContain('chat/completions')
  })

  test('default model is set', () => {
    const MISTRAL_DEFAULT_MODEL = 'mistral-large-latest'
    expect(MISTRAL_DEFAULT_MODEL).toBeDefined()
    expect(MISTRAL_DEFAULT_MODEL.length).toBeGreaterThan(0)
  })
})

describe('Subscription and Payment Processing', () => {
  const SUBSCRIPTION_PRICES = {
    starter_monthly_usd: 750,
    starter_yearly_usd: 7500,
    professional_monthly_usd: 2500,
    professional_yearly_usd: 25000,
    enterprise_monthly_usd: 7500,
    enterprise_yearly_usd: 75000
  }

  const TOPUP_PRICES = {
    topup_1_usd: 750,
    topup_2_usd: 2500,
    topup_3_usd: 7500,
    topup_4_usd: 12500
  }

  const TOPUP_TOKENS = {
    topup_1: 100_000,
    topup_2: 5_000_000,
    topup_3: 20_000_000,
    topup_4: 50_000_000
  }

  test('yearly discount is applied', () => {
    const monthly = SUBSCRIPTION_PRICES.starter_monthly_usd
    const yearly = SUBSCRIPTION_PRICES.starter_yearly_usd
    const expectedYearly = monthly * 10
    
    expect(yearly).toBeLessThan(monthly * 12)
    expect(yearly).toBe(expectedYearly)
  })

  test('enterprise costs more than professional', () => {
    expect(SUBSCRIPTION_PRICES.enterprise_monthly_usd).toBeGreaterThan(SUBSCRIPTION_PRICES.professional_monthly_usd)
    expect(SUBSCRIPTION_PRICES.professional_monthly_usd).toBeGreaterThan(SUBSCRIPTION_PRICES.starter_monthly_usd)
  })

  test('top-up tokens scale correctly', () => {
    expect(TOPUP_TOKENS.topup_2).toBeGreaterThan(TOPUP_TOKENS.topup_1)
    expect(TOPUP_TOKENS.topup_3).toBeGreaterThan(TOPUP_TOKENS.topup_2)
    expect(TOPUP_TOKENS.topup_4).toBeGreaterThan(TOPUP_TOKENS.topup_3)
  })

  test('top-up price per token decreases with volume', () => {
    const pricePerToken1 = TOPUP_PRICES.topup_1_usd / TOPUP_TOKENS.topup_1
    const pricePerToken4 = TOPUP_PRICES.topup_4_usd / TOPUP_TOKENS.topup_4
    
    expect(pricePerToken4).toBeLessThan(pricePerToken1)
  })

  test('calculates subscription value', () => {
    const monthlyValue = SUBSCRIPTION_PRICES.professional_monthly_usd / 100
    const yearlyValue = SUBSCRIPTION_PRICES.professional_yearly_usd / (100 * 12)
    
    expect(monthlyValue).toBe(25)
    expect(yearlyValue).toBeCloseTo(20.83, 1)
  })

  test('validates currency support', () => {
    const SUPPORTED_CURRENCIES = ['usd', 'gbp', 'eur']
    
    expect(SUPPORTED_CURRENCIES).toContain('usd')
    expect(SUPPORTED_CURRENCIES).toContain('gbp')
    expect(SUPPORTED_CURRENCIES).toContain('eur')
    expect(SUPPORTED_CURRENCIES.length).toBe(3)
  })

  test('validates billing cycles', () => {
    const BILLING_CYCLES = ['monthly', 'yearly']
    
    expect(BILLING_CYCLES).toContain('monthly')
    expect(BILLING_CYCLES).toContain('yearly')
    expect(BILLING_CYCLES.length).toBe(2)
  })
})
