import { describe, it, expect } from 'vitest'
import { ToolingError, type ToolingErrorType } from '../types'
import { ExecutionError } from '../../base/errors'

describe('ToolingError', () => {
  describe('inheritance', () => {
    it('extends ExecutionError', () => {
      const error = new ToolingError('service_unavailable', 'Service is down')
      expect(error).toBeInstanceOf(ExecutionError)
      expect(error).toBeInstanceOf(Error)
    })

    it('has tool category', () => {
      const error = new ToolingError('api_error', 'API failed')
      expect(error.category).toBe('tool')
    })
  })

  describe('severity mapping - critical errors', () => {
    const criticalErrorTypes: ToolingErrorType[] = [
      'credentials_missing',
      'credentials_invalid',
      'permission_denied',
      'auth_error',
      'adapter_not_found',
      'method_not_found',
      'tool_not_found',
    ]

    criticalErrorTypes.forEach((errorType) => {
      it(`${errorType} has critical severity`, () => {
        const error = new ToolingError(errorType, 'test message')
        expect(error.severity).toBe('critical')
      })

      it(`${errorType} is not retryable`, () => {
        const error = new ToolingError(errorType, 'test message')
        expect(error.isRetryable()).toBe(false)
      })
    })
  })

  describe('severity mapping - info errors', () => {
    it('validation_error has info severity', () => {
      const error = new ToolingError('validation_error', 'Invalid input')
      expect(error.severity).toBe('info')
    })

    it('validation_error is not retryable', () => {
      const error = new ToolingError('validation_error', 'Invalid input')
      expect(error.isRetryable()).toBe(false)
    })
  })

  describe('severity mapping - warning errors (retryable)', () => {
    const warningErrorTypes: ToolingErrorType[] = [
      'service_unavailable',
      'server_unavailable',
      'rate_limit_exceeded',
      'api_error',
      'network_error',
      'timeout_error',
      'unknown_error',
    ]

    warningErrorTypes.forEach((errorType) => {
      it(`${errorType} has warning severity`, () => {
        const error = new ToolingError(errorType, 'test message')
        expect(error.severity).toBe('warning')
      })

      // Check retryability
      const retryableTypes = [
        'service_unavailable',
        'server_unavailable',
        'rate_limit_exceeded',
        'api_error',
        'network_error',
        'timeout_error',
      ]

      if (retryableTypes.includes(errorType)) {
        it(`${errorType} is retryable`, () => {
          const error = new ToolingError(errorType, 'test message')
          expect(error.isRetryable()).toBe(true)
        })
      } else {
        it(`${errorType} is not retryable`, () => {
          const error = new ToolingError(errorType, 'test message')
          expect(error.isRetryable()).toBe(false)
        })
      }
    })
  })

  describe('complete severity mapping coverage', () => {
    it('all 15 error types have defined severity', () => {
      const allErrorTypes: ToolingErrorType[] = [
        // Critical (7)
        'credentials_missing',
        'credentials_invalid',
        'permission_denied',
        'auth_error',
        'adapter_not_found',
        'method_not_found',
        'tool_not_found',
        // Info (1)
        'validation_error',
        // Warning (7)
        'service_unavailable',
        'server_unavailable',
        'rate_limit_exceeded',
        'api_error',
        'network_error',
        'timeout_error',
        'unknown_error',
      ]

      expect(allErrorTypes).toHaveLength(15)

      // Verify each type has a valid severity
      allErrorTypes.forEach((type) => {
        const error = new ToolingError(type, 'test')
        expect(['critical', 'warning', 'info']).toContain(error.severity)
      })
    })
  })

  describe('error properties', () => {
    it('stores type property', () => {
      const error = new ToolingError('api_error', 'API failed')
      expect(error.type).toBe('tooling_error')
    })

    it('stores errorType property', () => {
      const error = new ToolingError('api_error', 'API failed')
      expect(error.errorType).toBe('api_error')
    })

    it('stores message', () => {
      const error = new ToolingError('network_error', 'Network timeout')
      expect(error.message).toBe('Network timeout')
    })

    it('stores details', () => {
      const details = { statusCode: 500, endpoint: '/api/test' }
      const error = new ToolingError('api_error', 'API failed', details)
      expect(error.details).toEqual(details)
    })

    it('includes type and details in context', () => {
      const details = { statusCode: 429 }
      const error = new ToolingError('rate_limit_exceeded', 'Rate limit hit', details)
      expect(error.context).toEqual({
        type: 'rate_limit_exceeded',
        details: { statusCode: 429 },
      })
    })

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

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

  describe('toJSON', () => {
    it('includes all error properties', () => {
      const details = { foo: 'bar' }
      const error = new ToolingError('api_error', 'test message', details)
      const json = error.toJSON()

      expect(json).toEqual({
        name: 'ToolingError',
        type: 'api_error',  // toJSON returns errorType for backward compatibility
        message: 'test message',
        severity: 'warning',
        category: 'tool',
        details: { foo: 'bar' },
      })
    })

    it('includes severity from getter', () => {
      const criticalError = new ToolingError('credentials_missing', 'No creds')
      expect(criticalError.toJSON().severity).toBe('critical')

      const warningError = new ToolingError('api_error', 'API failed')
      expect(warningError.toJSON().severity).toBe('warning')

      const infoError = new ToolingError('validation_error', 'Invalid')
      expect(infoError.toJSON().severity).toBe('info')
    })
  })

  describe('retryability', () => {
    it('retryable errors return true', () => {
      const retryableTypes: ToolingErrorType[] = [
        'service_unavailable',
        'rate_limit_exceeded',
        'api_error',
        'network_error',
        'timeout_error',
        'server_unavailable',
      ]

      retryableTypes.forEach((type) => {
        const error = new ToolingError(type, 'test')
        expect(error.isRetryable()).toBe(true)
      })
    })

    it('non-retryable errors return false', () => {
      const nonRetryableTypes: ToolingErrorType[] = [
        'credentials_missing',
        'credentials_invalid',
        'permission_denied',
        'auth_error',
        'adapter_not_found',
        'method_not_found',
        'tool_not_found',
        'validation_error',
        'unknown_error',
      ]

      nonRetryableTypes.forEach((type) => {
        const error = new ToolingError(type, 'test')
        expect(error.isRetryable()).toBe(false)
      })
    })
  })

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

    it('preserves stack trace origin', () => {
      function throwToolingError() {
        throw new ToolingError('network_error', 'test')
      }

      try {
        throwToolingError()
      } catch (error) {
        expect(error).toBeInstanceOf(ToolingError)
        expect((error as Error).stack).toContain('throwToolingError')
      }
    })
  })

  describe('instanceof checks', () => {
    it('maintains inheritance chain', () => {
      const error = new ToolingError('api_error', 'test')
      expect(error instanceof ToolingError).toBe(true)
      expect(error instanceof ExecutionError).toBe(true)
      expect(error instanceof Error).toBe(true)
    })
  })
})
