import { describe, it, expect } from 'vitest'
import {
  LLMError,
  InsufficientTokensError,
  TokenLimitExceededError,
  LLMResponseParseError,
  ModelConfigError,
} from '../errors'
import { ExecutionError } from '../../base/errors'

describe('LLM Errors', () => {
  describe('LLMError', () => {
    it('extends ExecutionError', () => {
      const error = new LLMError('test error')
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has warning severity', () => {
      const error = new LLMError('test error')
      expect(error.severity).toBe('warning')
    })

    it('has llm category', () => {
      const error = new LLMError('test error')
      expect(error.category).toBe('llm')
    })

    it('is retryable', () => {
      const error = new LLMError('test error')
      expect(error.isRetryable()).toBe(true)
    })

    it('sets message correctly', () => {
      const error = new LLMError('LLM service unavailable')
      expect(error.message).toBe('LLM service unavailable')
    })

    it('sets name to LLMError', () => {
      const error = new LLMError('test error')
      expect(error.name).toBe('LLMError')
    })

    it('stores context', () => {
      const context = { provider: 'anthropic', model: 'claude-3' }
      const error = new LLMError('test error', context)
      expect(error.context).toEqual(context)
    })

    it('returns correct error type', () => {
      const error = new LLMError('test error')
      expect(error.name).toBe('LLMError')
    })
  })

  describe('InsufficientTokensError', () => {
    it('extends LLMError', () => {
      const error = new InsufficientTokensError('insufficient tokens')
      expect(error).toBeInstanceOf(LLMError)
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has critical severity (overrides LLMError warning)', () => {
      const error = new InsufficientTokensError('insufficient tokens')
      expect(error.severity).toBe('critical')
    })

    it('has llm category (inherited from LLMError)', () => {
      const error = new InsufficientTokensError('insufficient tokens')
      expect(error.category).toBe('llm')
    })

    it('is not retryable (overrides LLMError retryable)', () => {
      const error = new InsufficientTokensError('insufficient tokens')
      expect(error.isRetryable()).toBe(false)
    })

    it('sets message correctly', () => {
      const error = new InsufficientTokensError('Need 1000 tokens, have 500')
      expect(error.message).toBe('Need 1000 tokens, have 500')
    })

    it('sets name to InsufficientTokensError', () => {
      const error = new InsufficientTokensError('insufficient tokens')
      expect(error.name).toBe('InsufficientTokensError')
    })

    it('stores context with token information', () => {
      const context = { required: 1000, available: 500 }
      const error = new InsufficientTokensError('insufficient tokens', context)
      expect(error.context).toEqual(context)
    })
  })

  describe('TokenLimitExceededError', () => {
    it('extends LLMError', () => {
      const error = new TokenLimitExceededError('token limit exceeded')
      expect(error).toBeInstanceOf(LLMError)
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has critical severity (overrides LLMError warning)', () => {
      const error = new TokenLimitExceededError('token limit exceeded')
      expect(error.severity).toBe('critical')
    })

    it('has llm category (inherited from LLMError)', () => {
      const error = new TokenLimitExceededError('token limit exceeded')
      expect(error.category).toBe('llm')
    })

    it('is not retryable (overrides LLMError retryable)', () => {
      const error = new TokenLimitExceededError('token limit exceeded')
      expect(error.isRetryable()).toBe(false)
    })

    it('sets message correctly', () => {
      const error = new TokenLimitExceededError('Exceeded max tokens: 4096')
      expect(error.message).toBe('Exceeded max tokens: 4096')
    })

    it('sets name to TokenLimitExceededError', () => {
      const error = new TokenLimitExceededError('token limit exceeded')
      expect(error.name).toBe('TokenLimitExceededError')
    })

    it('stores context with limit information', () => {
      const context = { limit: 4096, attempted: 5000 }
      const error = new TokenLimitExceededError('token limit exceeded', context)
      expect(error.context).toEqual(context)
    })
  })

  describe('LLMResponseParseError', () => {
    it('extends LLMError', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error).toBeInstanceOf(LLMError)
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has warning severity', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error.severity).toBe('warning')
    })

    it('has llm category (inherited from LLMError)', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error.category).toBe('llm')
    })

    it('is retryable (transient JSON parse failures can succeed on retry)', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error.isRetryable()).toBe(true)
    })

    it('sets message correctly', () => {
      const error = new LLMResponseParseError('Unterminated string in JSON at position 100')
      expect(error.message).toBe('Unterminated string in JSON at position 100')
    })

    it('sets name to LLMResponseParseError', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error.name).toBe('LLMResponseParseError')
    })

    it('stores context with raw content and parse error', () => {
      const context = { rawContent: '{"incomplete', parseError: 'Unexpected end of JSON input' }
      const error = new LLMResponseParseError('failed to parse JSON', context)
      expect(error.context).toEqual(context)
    })

    it('has correct type property', () => {
      const error = new LLMResponseParseError('failed to parse JSON')
      expect(error.type).toBe('llm_response_parse_error')
    })
  })

  describe('ModelConfigError', () => {
    it('extends ExecutionError', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has warning severity', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.severity).toBe('warning')
    })

    it('has validation category', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.category).toBe('validation')
    })

    it('is not retryable', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.isRetryable()).toBe(false)
    })

    it('sets message correctly', () => {
      const error = new ModelConfigError('Temperature must be between 0 and 2', 'temperature', 'gpt-4')
      expect(error.message).toBe('Temperature must be between 0 and 2')
    })

    it('sets name to ModelConfigError', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.name).toBe('ModelConfigError')
    })

    it('stores field property', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.field).toBe('temperature')
    })

    it('stores model property', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.model).toBe('gpt-4')
    })

    it('includes field and model in context', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.context).toEqual({ field: 'temperature', model: 'gpt-4' })
    })

    it('merges additional context with field and model', () => {
      const additionalContext = { value: 3.5, min: 0, max: 2 }
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4', additionalContext)
      expect(error.context).toEqual({
        field: 'temperature',
        model: 'gpt-4',
        value: 3.5,
        min: 0,
        max: 2,
      })
    })

    it('returns correct error type', () => {
      const error = new ModelConfigError('invalid config', 'temperature', 'gpt-4')
      expect(error.name).toBe('ModelConfigError')
    })
  })

  describe('error hierarchy', () => {
    it('LLMError instanceof checks', () => {
      const error = new LLMError('test')
      expect(error instanceof LLMError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
    })

    it('InsufficientTokensError instanceof checks', () => {
      const error = new InsufficientTokensError('test')
      expect(error instanceof InsufficientTokensError).toBe(true)
      expect(error instanceof LLMError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
    })

    it('TokenLimitExceededError instanceof checks', () => {
      const error = new TokenLimitExceededError('test')
      expect(error instanceof TokenLimitExceededError).toBe(true)
      expect(error instanceof LLMError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
    })

    it('LLMResponseParseError instanceof checks', () => {
      const error = new LLMResponseParseError('test')
      expect(error instanceof LLMResponseParseError).toBe(true)
      expect(error instanceof LLMError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
    })

    it('ModelConfigError instanceof checks', () => {
      const error = new ModelConfigError('test', 'field', 'model')
      expect(error instanceof ModelConfigError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
      expect(error instanceof LLMError).toBe(false)
    })
  })

  describe('stack traces', () => {
    it('LLMError captures stack trace', () => {
      const error = new LLMError('test')
      expect(error.stack).toBeDefined()
      expect(error.stack).toContain('LLMError')
    })

    it('InsufficientTokensError captures stack trace', () => {
      const error = new InsufficientTokensError('test')
      expect(error.stack).toBeDefined()
      expect(error.stack).toContain('InsufficientTokensError')
    })

    it('TokenLimitExceededError captures stack trace', () => {
      const error = new TokenLimitExceededError('test')
      expect(error.stack).toBeDefined()
      expect(error.stack).toContain('TokenLimitExceededError')
    })

    it('LLMResponseParseError captures stack trace', () => {
      const error = new LLMResponseParseError('test')
      expect(error.stack).toBeDefined()
      expect(error.stack).toContain('LLMResponseParseError')
    })

    it('ModelConfigError captures stack trace', () => {
      const error = new ModelConfigError('test', 'field', 'model')
      expect(error.stack).toBeDefined()
      expect(error.stack).toContain('ModelConfigError')
    })
  })
})
