/**
 * Thinking Validator Unit Tests
 *
 * Tests for thinking budget validation logic
 */

import { describe, it, expect } from 'bun:test';
import {
  validateThinking,
  THINKING_LEVEL_BUDGETS,
  VALID_THINKING_LEVELS,
  THINKING_OFF_VALUES,
  THINKING_BUDGET_MIN,
  THINKING_BUDGET_MAX,
  THINKING_BUDGET_DEFAULT_MIN,
} from '../../../src/cliproxy/thinking-validator';

describe('Thinking Validator', () => {
  describe('Constants', () => {
    it('should export valid budget bounds', () => {
      expect(THINKING_BUDGET_MIN).toBe(0);
      expect(THINKING_BUDGET_MAX).toBe(100000);
      expect(THINKING_BUDGET_DEFAULT_MIN).toBe(512);
    });

    it('should export valid thinking levels', () => {
      expect(VALID_THINKING_LEVELS).toContain('minimal');
      expect(VALID_THINKING_LEVELS).toContain('low');
      expect(VALID_THINKING_LEVELS).toContain('medium');
      expect(VALID_THINKING_LEVELS).toContain('high');
      expect(VALID_THINKING_LEVELS).toContain('xhigh');
      expect(VALID_THINKING_LEVELS).toContain('max');
      expect(VALID_THINKING_LEVELS).toContain('auto');
    });

    it('should export level budget mappings', () => {
      expect(THINKING_LEVEL_BUDGETS.minimal).toBe(512);
      expect(THINKING_LEVEL_BUDGETS.low).toBe(1024);
      expect(THINKING_LEVEL_BUDGETS.medium).toBe(8192);
      expect(THINKING_LEVEL_BUDGETS.high).toBe(24576);
      expect(THINKING_LEVEL_BUDGETS.xhigh).toBe(32768);
      // `max` sits above xhigh — unconstrained thinking on Opus 4.7 / Mythos
      expect(THINKING_LEVEL_BUDGETS.max).toBeGreaterThan(THINKING_LEVEL_BUDGETS.xhigh);
    });

    it('should treat max as a distinct top tier on models that list it (Opus 4.7)', () => {
      // Opus 4.7 exposes both xhigh and max; max must not collapse into xhigh.
      const result = validateThinking('claude', 'claude-opus-4-7', 'max');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('max');
      expect(result.warning).toBeUndefined();
    });

    it('should still alias max -> xhigh for models without a max level (backcompat)', () => {
      // Codex catalog uses ['low','medium','high','xhigh'] with maxLevel 'xhigh'.
      // User input "max" should map down to xhigh rather than be rejected.
      const result = validateThinking('codex', 'gpt-5.4', 'max');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('xhigh');
    });

    it('should export off values', () => {
      expect(THINKING_OFF_VALUES).toContain('off');
      expect(THINKING_OFF_VALUES).toContain('none');
      expect(THINKING_OFF_VALUES).toContain('disabled');
      expect(THINKING_OFF_VALUES).toContain('0');
    });
  });

  describe('Off values', () => {
    it('should handle "off" value', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 'off');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('off');
      expect(result.warning).toBeUndefined();
    });

    it('should handle "none" value', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 'none');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('off');
    });

    it('should handle "disabled" value', () => {
      const result = validateThinking('agy', 'claude-sonnet-4-20250514', 'disabled');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('off');
    });

    it('should handle "0" string as off', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', '0');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('off');
    });

    it('should be case-insensitive for off values', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 'OFF');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('off');
    });
  });

  describe('Named levels (levels-type models like Gemini)', () => {
    it('should accept valid level names', () => {
      const levels = ['low', 'medium', 'high'];
      for (const level of levels) {
        const result = validateThinking('gemini', 'gemini-3-pro-preview', level);
        expect(result.valid).toBe(true);
      }
    });

    it('should be case-insensitive for level names', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 'HIGH');
      expect(result.valid).toBe(true);
    });

    it('should map numeric budgets to closest level for level-type models', () => {
      // Level-type models convert budget to closest named level
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 8192);
      expect(result.valid).toBe(true);
      expect(typeof result.value).toBe('string'); // Should be a level name
      expect(result.warning).toContain('Mapped');
    });

    it('caps unsupported higher standard levels to the highest supported model level', () => {
      const result = validateThinking('codex', 'gpt-5-mini', 'xhigh');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('high');
      expect(result.warning).toContain('mapped to "high"');
    });
  });

  describe('Budget-type models (like Claude via agy)', () => {
    // Claude models via agy use budget-type thinking
    const budgetModel = 'claude-sonnet-4-5-thinking';

    it('should accept valid numeric budget', () => {
      const result = validateThinking('agy', budgetModel, 8192);
      expect(result.valid).toBe(true);
      expect(result.value).toBe(8192);
    });

    it('should accept numeric string budget', () => {
      const result = validateThinking('agy', budgetModel, '8192');
      expect(result.valid).toBe(true);
      expect(result.value).toBe(8192);
    });

    it('should reject negative budgets', () => {
      const result = validateThinking('agy', budgetModel, -100);
      expect(result.valid).toBe(false);
      expect(result.warning).toContain('Negative');
    });

    it('should clamp excessively high budgets', () => {
      const result = validateThinking('agy', budgetModel, 999999999);
      expect(result.valid).toBe(true);
      expect(result.warning).toContain('Clamped');
    });

    it('should reject partial numeric parses like "123abc"', () => {
      const result = validateThinking('agy', budgetModel, '123abc');
      // Should either fail or find closest level match, not parse as 123
      expect(result.value).not.toBe(123);
    });
  });

  describe('Auto value', () => {
    it('should accept auto value', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', 'auto');
      expect(result.valid).toBe(true);
      expect(result.value).toBe('auto');
    });
  });

  describe('Unknown models', () => {
    it('should pass through value for unknown models with warning', () => {
      const result = validateThinking('gemini', 'unknown-model-xyz', 'high');
      expect(result.valid).toBe(true);
      expect(result.warning).toContain('unknown');
    });
  });

  describe('Edge cases', () => {
    it('should handle whitespace in input', () => {
      const result = validateThinking('gemini', 'gemini-3-pro-preview', '  high  ');
      expect(result.valid).toBe(true);
    });

    it('should handle empty string by passing through for unknown', () => {
      // Empty string on known model goes to level/budget validation
      const result = validateThinking('gemini', 'gemini-3-pro-preview', '');
      // For level-type models, empty string will try to match levels
      expect(result.valid).toBeDefined(); // Just check it doesn't crash
    });
  });
});
