/**
 * Model Config Validation Tests
 * Verifies consolidated model configuration validation (constraints + options)
 */

import { describe, it, expect } from 'vitest'
import { validateModelConfig, ModelConfigError } from '../errors'

describe('validateModelConfig', () => {
  describe('Valid Configurations', () => {
    it('passes for gpt-5 with complete config', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 8000
        })
      }).not.toThrow()
    })

    it('passes for gpt-5.4-mini with complete config', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5.4-mini',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 8000
        })
      }).not.toThrow()
    })

    it('passes for gpt-5 with model options', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 8000,
          modelOptions: {
            reasoning_effort: 'low',
            verbosity: 'high'
          }
        })
      }).not.toThrow()
    })

    it('passes for mock model with any temperature', () => {
      expect(() => {
        validateModelConfig({
          model: 'mock',
          provider: 'mock',
          apiKey: 'test-key',
          temperature: 0.7,
          maxOutputTokens: 1000
        })
      }).not.toThrow()
    })

    it('passes for config without maxOutputTokens', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1
        })
      }).not.toThrow()
    })

    it('passes for config without topP', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 8000
        })
      }).not.toThrow()
    })
  })

  describe('Invalid Temperature', () => {
    it('throws for gpt-5 with temperature=0.7', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 0.7,
          maxOutputTokens: 8000
        })
      }).toThrow(ModelConfigError)
    })

    it('throws for gpt-5.4-mini with temperature=0.5', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5.4-mini',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 0.5,
          maxOutputTokens: 8000
        })
      }).toThrow(ModelConfigError)
    })

    it('provides correct error details for invalid temperature', () => {
      try {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 0.7,
          maxOutputTokens: 8000
        })
        expect.fail('Should have thrown')
      } catch (error) {
        expect(error).toBeInstanceOf(ModelConfigError)
        const modelError = error as ModelConfigError
        expect(modelError.field).toBe('temperature')
        expect(modelError.model).toBe('gpt-5')
      }
    })
  })

  describe('Invalid maxOutputTokens', () => {
    it('throws for maxOutputTokens below minimum', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 2000
        })
      }).toThrow(ModelConfigError)
    })

    it('throws for mock model with maxOutputTokens below minimum', () => {
      expect(() => {
        validateModelConfig({
          model: 'mock',
          provider: 'mock',
          apiKey: 'test-key',
          maxOutputTokens: 100
        })
      }).toThrow(ModelConfigError)
    })

    it('provides correct error details for invalid maxOutputTokens', () => {
      try {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          maxOutputTokens: 2000
        })
        expect.fail('Should have thrown')
      } catch (error) {
        expect(error).toBeInstanceOf(ModelConfigError)
        const modelError = error as ModelConfigError
        expect(modelError.field).toBe('maxOutputTokens')
        expect(modelError.model).toBe('gpt-5')
      }
    })
  })

  describe('Invalid Model Options', () => {
    it('throws for invalid reasoning_effort value', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          modelOptions: {
            reasoning_effort: 'extreme'
          }
        })
      }).toThrow(ModelConfigError)
    })

    it('throws for invalid verbosity value', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          modelOptions: {
            verbosity: 'ultra'
          }
        })
      }).toThrow(ModelConfigError)
    })

    it('throws for mock model with options', () => {
      expect(() => {
        validateModelConfig({
          model: 'mock',
          provider: 'mock',
          apiKey: 'test-key',
          modelOptions: {
            reasoning_effort: 'low'
          }
        })
      }).toThrow(ModelConfigError)
    })

    it('provides correct error details for invalid option', () => {
      try {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: 'test-key',
          temperature: 1,
          modelOptions: {
            reasoning_effort: 'invalid'
          }
        })
        expect.fail('Should have thrown')
      } catch (error) {
        expect(error).toBeInstanceOf(ModelConfigError)
        const modelError = error as ModelConfigError
        expect(modelError.model).toBe('gpt-5')
      }
    })
  })

  describe('Unknown Model', () => {
    it('throws for unknown model', () => {
      expect(() => {
        validateModelConfig({
          model: 'unknown-model'
        })
      }).toThrow(ModelConfigError)
    })

    it('provides correct error details for unknown model', () => {
      try {
        validateModelConfig({
          model: 'unknown-model'
        })
        expect.fail('Should have thrown')
      } catch (error) {
        expect(error).toBeInstanceOf(ModelConfigError)
        const modelError = error as ModelConfigError
        expect(modelError.field).toBe('model')
        expect(modelError.model).toBe('unknown-model')
        expect(modelError.message).toContain('Unknown model')
      }
    })
  })

  describe('Missing Required Fields', () => {
    it('throws for missing provider', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          apiKey: 'test-key',
          temperature: 1
        })
      }).toThrow(ModelConfigError)
    })

    it('throws for missing apiKey', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          temperature: 1
        })
      }).toThrow(ModelConfigError)
    })

    it('passes for empty apiKey (resolved at runtime via process.env)', () => {
      expect(() => {
        validateModelConfig({
          model: 'gpt-5',
          provider: 'openai',
          apiKey: '',
          temperature: 1
        })
      }).not.toThrow()
    })
  })

  describe('ModelConfigError Structure', () => {
    it('has correct properties', () => {
      const error = new ModelConfigError('Test message', 'temperature', 'gpt-5')

      expect(error).toBeInstanceOf(Error)
      expect(error.name).toBe('ModelConfigError')
      expect(error.message).toBe('Test message')
      expect(error.field).toBe('temperature')
      expect(error.model).toBe('gpt-5')
    })

    it('is catchable as Error', () => {
      try {
        throw new ModelConfigError('Test', 'field', 'model')
      } catch (error) {
        expect(error).toBeInstanceOf(Error)
      }
    })

    it('is catchable as ModelConfigError', () => {
      try {
        throw new ModelConfigError('Test', 'field', 'model')
      } catch (error) {
        expect(error).toBeInstanceOf(ModelConfigError)
      }
    })
  })
})
