/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect } from 'vitest';
import {
  ModelConfigService,
  type ModelConfigAlias,
  type ModelConfigServiceConfig,
} from './modelConfigService.js';

describe('ModelConfigService', () => {
  it('should resolve a basic alias to its model and settings', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {
        classifier: {
          modelConfig: {
            model: 'gemini-1.5-flash-latest',
            generateContentConfig: {
              temperature: 0,
              topP: 0.9,
            },
          },
        },
      },
      overrides: [],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({ model: 'classifier' });

    expect(resolved.model).toBe('gemini-1.5-flash-latest');
    expect(resolved.generateContentConfig).toEqual({
      temperature: 0,
      topP: 0.9,
    });
  });

  it('should apply a simple override on top of an alias', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {
        classifier: {
          modelConfig: {
            model: 'gemini-1.5-flash-latest',
            generateContentConfig: {
              temperature: 0,
              topP: 0.9,
            },
          },
        },
      },
      overrides: [
        {
          match: { model: 'classifier' },
          modelConfig: {
            generateContentConfig: {
              temperature: 0.5,
              maxOutputTokens: 1000,
            },
          },
        },
      ],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({ model: 'classifier' });

    expect(resolved.model).toBe('gemini-1.5-flash-latest');
    expect(resolved.generateContentConfig).toEqual({
      temperature: 0.5,
      topP: 0.9,
      maxOutputTokens: 1000,
    });
  });

  it('should apply the most specific override rule', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {},
      overrides: [
        {
          match: { model: 'gemini-pro' },
          modelConfig: { generateContentConfig: { temperature: 0.5 } },
        },
        {
          match: { model: 'gemini-pro', overrideScope: 'my-agent' },
          modelConfig: { generateContentConfig: { temperature: 0.1 } },
        },
      ],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({
      model: 'gemini-pro',
      overrideScope: 'my-agent',
    });

    expect(resolved.model).toBe('gemini-pro');
    expect(resolved.generateContentConfig).toEqual({ temperature: 0.1 });
  });

  it('should use the last override in case of a tie in specificity', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {},
      overrides: [
        {
          match: { model: 'gemini-pro' },
          modelConfig: {
            generateContentConfig: { temperature: 0.5, topP: 0.8 },
          },
        },
        {
          match: { model: 'gemini-pro' },
          modelConfig: { generateContentConfig: { temperature: 0.1 } },
        },
      ],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({ model: 'gemini-pro' });

    expect(resolved.model).toBe('gemini-pro');
    expect(resolved.generateContentConfig).toEqual({
      temperature: 0.1,
      topP: 0.8,
    });
  });

  it('should correctly pass through generation config from an alias', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {
        'thinking-alias': {
          modelConfig: {
            model: 'gemini-pro',
            generateContentConfig: {
              candidateCount: 500,
            },
          },
        },
      },
      overrides: [],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({ model: 'thinking-alias' });

    expect(resolved.generateContentConfig).toEqual({ candidateCount: 500 });
  });

  it('should let an override generation config win over an alias config', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {
        'thinking-alias': {
          modelConfig: {
            model: 'gemini-pro',
            generateContentConfig: {
              candidateCount: 500,
            },
          },
        },
      },
      overrides: [
        {
          match: { model: 'thinking-alias' },
          modelConfig: {
            generateContentConfig: {
              candidateCount: 1000,
            },
          },
        },
      ],
    };
    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({ model: 'thinking-alias' });

    expect(resolved.generateContentConfig).toEqual({
      candidateCount: 1000,
    });
  });

  it('should merge settings from global, alias, and multiple matching overrides', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {
        'test-alias': {
          modelConfig: {
            model: 'gemini-test-model',
            generateContentConfig: {
              topP: 0.9,
              topK: 50,
            },
          },
        },
      },
      overrides: [
        {
          match: { model: 'gemini-test-model' },
          modelConfig: {
            generateContentConfig: {
              topK: 40,
              maxOutputTokens: 2048,
            },
          },
        },
        {
          match: { overrideScope: 'test-agent' },
          modelConfig: {
            generateContentConfig: {
              maxOutputTokens: 4096,
            },
          },
        },
        {
          match: { model: 'gemini-test-model', overrideScope: 'test-agent' },
          modelConfig: {
            generateContentConfig: {
              temperature: 0.2,
            },
          },
        },
      ],
    };

    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({
      model: 'test-alias',
      overrideScope: 'test-agent',
    });

    expect(resolved.model).toBe('gemini-test-model');
    expect(resolved.generateContentConfig).toEqual({
      // From global, overridden by most specific override
      temperature: 0.2,
      // From alias, not overridden
      topP: 0.9,
      // From alias, overridden by less specific override
      topK: 40,
      // From first matching override, overridden by second matching override
      maxOutputTokens: 4096,
    });
  });

  it('should match an agent:core override when agent is undefined', () => {
    const config: ModelConfigServiceConfig = {
      aliases: {},
      overrides: [
        {
          match: { overrideScope: 'core' },
          modelConfig: {
            generateContentConfig: {
              temperature: 0.1,
            },
          },
        },
      ],
    };

    const service = new ModelConfigService(config);
    const resolved = service.getResolvedConfig({
      model: 'gemini-pro',
      overrideScope: undefined, // Explicitly undefined
    });

    expect(resolved.model).toBe('gemini-pro');
    expect(resolved.generateContentConfig).toEqual({
      temperature: 0.1,
    });
  });

  describe('alias inheritance', () => {
    it('should resolve a simple "extends" chain', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          base: {
            modelConfig: {
              model: 'gemini-1.5-pro-latest',
              generateContentConfig: {
                temperature: 0.7,
                topP: 0.9,
              },
            },
          },
          'flash-variant': {
            extends: 'base',
            modelConfig: {
              model: 'gemini-1.5-flash-latest',
            },
          },
        },
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'flash-variant' });

      expect(resolved.model).toBe('gemini-1.5-flash-latest');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.7,
        topP: 0.9,
      });
    });

    it('should override parent properties from child alias', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          base: {
            modelConfig: {
              model: 'gemini-1.5-pro-latest',
              generateContentConfig: {
                temperature: 0.7,
                topP: 0.9,
              },
            },
          },
          'flash-variant': {
            extends: 'base',
            modelConfig: {
              model: 'gemini-1.5-flash-latest',
              generateContentConfig: {
                temperature: 0.2,
              },
            },
          },
        },
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'flash-variant' });

      expect(resolved.model).toBe('gemini-1.5-flash-latest');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.2,
        topP: 0.9,
      });
    });

    it('should resolve a multi-level "extends" chain', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          base: {
            modelConfig: {
              model: 'gemini-1.5-pro-latest',
              generateContentConfig: {
                temperature: 0.7,
                topP: 0.9,
              },
            },
          },
          'base-flash': {
            extends: 'base',
            modelConfig: {
              model: 'gemini-1.5-flash-latest',
            },
          },
          'classifier-flash': {
            extends: 'base-flash',
            modelConfig: {
              generateContentConfig: {
                temperature: 0,
              },
            },
          },
        },
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({
        model: 'classifier-flash',
      });

      expect(resolved.model).toBe('gemini-1.5-flash-latest');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0,
        topP: 0.9,
      });
    });

    it('should throw an error for circular dependencies', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          a: { extends: 'b', modelConfig: {} },
          b: { extends: 'a', modelConfig: {} },
        },
      };
      const service = new ModelConfigService(config);
      expect(() => service.getResolvedConfig({ model: 'a' })).toThrow(
        'Circular alias dependency: a -> b -> a',
      );
    });

    describe('abstract aliases', () => {
      it('should allow an alias to extend an abstract alias without a model', () => {
        const config: ModelConfigServiceConfig = {
          aliases: {
            'abstract-base': {
              modelConfig: {
                generateContentConfig: {
                  temperature: 0.1,
                },
              },
            },
            'concrete-child': {
              extends: 'abstract-base',
              modelConfig: {
                model: 'gemini-1.5-pro-latest',
                generateContentConfig: {
                  topP: 0.9,
                },
              },
            },
          },
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'concrete-child' });

        expect(resolved.model).toBe('gemini-1.5-pro-latest');
        expect(resolved.generateContentConfig).toEqual({
          temperature: 0.1,
          topP: 0.9,
        });
      });

      it('should throw an error if a resolved alias chain has no model', () => {
        const config: ModelConfigServiceConfig = {
          aliases: {
            'abstract-base': {
              modelConfig: {
                generateContentConfig: { temperature: 0.7 },
              },
            },
          },
        };
        const service = new ModelConfigService(config);
        expect(() =>
          service.getResolvedConfig({ model: 'abstract-base' }),
        ).toThrow(
          'Could not resolve a model name for alias "abstract-base". Please ensure the alias chain or a matching override specifies a model.',
        );
      });

      it('should resolve an abstract alias if an override provides the model', () => {
        const config: ModelConfigServiceConfig = {
          aliases: {
            'abstract-base': {
              modelConfig: {
                generateContentConfig: {
                  temperature: 0.1,
                },
              },
            },
          },
          overrides: [
            {
              match: { model: 'abstract-base' },
              modelConfig: {
                model: 'gemini-1.5-flash-latest',
              },
            },
          ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'abstract-base' });

        expect(resolved.model).toBe('gemini-1.5-flash-latest');
        expect(resolved.generateContentConfig).toEqual({
          temperature: 0.1,
        });
      });
    });

    it('should throw an error if an extended alias does not exist', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'bad-alias': {
            extends: 'non-existent',
            modelConfig: {},
          },
        },
      };
      const service = new ModelConfigService(config);
      expect(() => service.getResolvedConfig({ model: 'bad-alias' })).toThrow(
        'Alias "non-existent" not found.',
      );
    });

    it('should throw an error if the alias chain is too deep', () => {
      const aliases: Record<string, ModelConfigAlias> = {};
      for (let i = 0; i < 101; i++) {
        aliases[`alias-${i}`] = {
          extends: i === 100 ? undefined : `alias-${i + 1}`,
          modelConfig: i === 100 ? { model: 'gemini-pro' } : {},
        };
      }
      const config: ModelConfigServiceConfig = { aliases };
      const service = new ModelConfigService(config);
      expect(() => service.getResolvedConfig({ model: 'alias-0' })).toThrow(
        'Alias inheritance chain exceeded maximum depth of 100.',
      );
    });
  });

  describe('deep merging', () => {
    it('should deep merge nested config objects from aliases and overrides', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'base-safe': {
            modelConfig: {
              model: 'gemini-pro',
              generateContentConfig: {
                safetySettings: {
                  HARM_CATEGORY_HARASSMENT: 'BLOCK_ONLY_HIGH',
                  HARM_CATEGORY_HATE_SPEECH: 'BLOCK_ONLY_HIGH',
                  // eslint-disable-next-line @typescript-eslint/no-explicit-any
                } as any,
              },
            },
          },
        },
        overrides: [
          {
            match: { model: 'base-safe' },
            modelConfig: {
              generateContentConfig: {
                safetySettings: {
                  HARM_CATEGORY_HATE_SPEECH: 'BLOCK_NONE',
                  HARM_CATEGORY_SEXUALLY_EXPLICIT: 'BLOCK_MEDIUM_AND_ABOVE',
                  // eslint-disable-next-line @typescript-eslint/no-explicit-any
                } as any,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'base-safe' });

      expect(resolved.model).toBe('gemini-pro');
      expect(resolved.generateContentConfig.safetySettings).toEqual({
        // From alias
        HARM_CATEGORY_HARASSMENT: 'BLOCK_ONLY_HIGH',
        // From alias, overridden by override
        HARM_CATEGORY_HATE_SPEECH: 'BLOCK_NONE',
        // From override
        HARM_CATEGORY_SEXUALLY_EXPLICIT: 'BLOCK_MEDIUM_AND_ABOVE',
      });
    });

    it('should not deeply merge merge arrays from aliases and overrides', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          base: {
            modelConfig: {
              model: 'gemini-pro',
              generateContentConfig: {
                stopSequences: ['foo'],
              },
            },
          },
        },
        overrides: [
          {
            match: { model: 'base' },
            modelConfig: {
              generateContentConfig: {
                stopSequences: ['overrideFoo'],
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'base' });

      expect(resolved.model).toBe('gemini-pro');
      expect(resolved.generateContentConfig.stopSequences).toEqual([
        'overrideFoo',
      ]);
    });
  });

  describe('runtime aliases', () => {
    it('should resolve a simple runtime-registered alias', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
        overrides: [],
      };
      const service = new ModelConfigService(config);

      service.registerRuntimeModelConfig('runtime-alias', {
        modelConfig: {
          model: 'gemini-runtime-model',
          generateContentConfig: {
            temperature: 0.123,
          },
        },
      });

      const resolved = service.getResolvedConfig({ model: 'runtime-alias' });

      expect(resolved.model).toBe('gemini-runtime-model');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.123,
      });
    });
  });

  describe('runtime overrides', () => {
    it('should resolve a simple runtime-registered override', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
        overrides: [],
      };
      const service = new ModelConfigService(config);

      service.registerRuntimeModelOverride({
        match: { model: 'gemini-pro' },
        modelConfig: {
          generateContentConfig: {
            temperature: 0.99,
          },
        },
      });

      const resolved = service.getResolvedConfig({ model: 'gemini-pro' });

      expect(resolved.model).toBe('gemini-pro');
      expect(resolved.generateContentConfig.temperature).toBe(0.99);
    });

    it('should prioritize runtime overrides over default overrides when they have the same specificity', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
        overrides: [
          {
            match: { model: 'gemini-pro' },
            modelConfig: { generateContentConfig: { temperature: 0.1 } },
          },
        ],
      };
      const service = new ModelConfigService(config);

      service.registerRuntimeModelOverride({
        match: { model: 'gemini-pro' },
        modelConfig: { generateContentConfig: { temperature: 0.9 } },
      });

      const resolved = service.getResolvedConfig({ model: 'gemini-pro' });

      // Runtime overrides are appended after overrides/customOverrides, so they should win.
      expect(resolved.generateContentConfig.temperature).toBe(0.9);
    });

    it('should still respect specificity with runtime overrides', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
        overrides: [],
      };
      const service = new ModelConfigService(config);

      // Register a more specific runtime override
      service.registerRuntimeModelOverride({
        match: { model: 'gemini-pro', overrideScope: 'my-agent' },
        modelConfig: { generateContentConfig: { temperature: 0.1 } },
      });

      // Register a less specific runtime override later
      service.registerRuntimeModelOverride({
        match: { model: 'gemini-pro' },
        modelConfig: { generateContentConfig: { temperature: 0.9 } },
      });

      const resolved = service.getResolvedConfig({
        model: 'gemini-pro',
        overrideScope: 'my-agent',
      });

      // Specificity should win over order
      expect(resolved.generateContentConfig.temperature).toBe(0.1);
    });
  });

  describe('custom aliases', () => {
    it('should resolve a custom alias', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
        customAliases: {
          'my-custom-alias': {
            modelConfig: {
              model: 'gemini-custom',
              generateContentConfig: {
                temperature: 0.9,
              },
            },
          },
        },
        overrides: [],
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'my-custom-alias' });

      expect(resolved.model).toBe('gemini-custom');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.9,
      });
    });

    it('should allow custom aliases to override built-in aliases', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'standard-alias': {
            modelConfig: {
              model: 'gemini-standard',
              generateContentConfig: {
                temperature: 0.5,
              },
            },
          },
        },
        customAliases: {
          'standard-alias': {
            modelConfig: {
              model: 'gemini-custom-override',
              generateContentConfig: {
                temperature: 0.1,
              },
            },
          },
        },
        overrides: [],
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'standard-alias' });

      expect(resolved.model).toBe('gemini-custom-override');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.1,
      });
    });
  });

  describe('fallback behavior', () => {
    it('should fallback to chat-base if the requested model is completely unknown', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'chat-base': {
            modelConfig: {
              model: 'default-fallback-model',
              generateContentConfig: {
                temperature: 0.99,
              },
            },
          },
        },
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({
        model: 'my-custom-model',
        isChatModel: true,
      });

      // It preserves the requested model name, but inherits the config from chat-base
      expect(resolved.model).toBe('my-custom-model');
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.99,
      });
    });

    it('should return empty config if requested model is unknown and chat-base is not defined', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {},
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({
        model: 'my-custom-model',
        isChatModel: true,
      });

      expect(resolved.model).toBe('my-custom-model');
      expect(resolved.generateContentConfig).toEqual({});
    });

    it('should NOT fallback to chat-base if the requested model is completely unknown but isChatModel is false', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'chat-base': {
            modelConfig: {
              model: 'default-fallback-model',
              generateContentConfig: {
                temperature: 0.99,
              },
            },
          },
        },
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({
        model: 'my-custom-model',
        isChatModel: false,
      });

      expect(resolved.model).toBe('my-custom-model');
      expect(resolved.generateContentConfig).toEqual({});
    });
  });

  describe('unrecognized models', () => {
    it('should apply overrides to unrecognized model names', () => {
      const unregisteredModelName = 'my-unregistered-model-v1';
      const config: ModelConfigServiceConfig = {
        aliases: {}, // No aliases defined
        overrides: [
          {
            match: { model: unregisteredModelName },
            modelConfig: {
              generateContentConfig: {
                temperature: 0.01,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);

      // Request the unregistered model directly
      const resolved = service.getResolvedConfig({
        model: unregisteredModelName,
      });

      // It should preserve the model name and apply the override
      expect(resolved.model).toBe(unregisteredModelName);
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.01,
      });
    });

    it('should apply scoped overrides to unrecognized model names', () => {
      const unregisteredModelName = 'my-unregistered-model-v1';
      const config: ModelConfigServiceConfig = {
        aliases: {},
        overrides: [
          {
            match: {
              model: unregisteredModelName,
              overrideScope: 'special-agent',
            },
            modelConfig: {
              generateContentConfig: {
                temperature: 0.99,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);

      const resolved = service.getResolvedConfig({
        model: unregisteredModelName,
        overrideScope: 'special-agent',
      });

      expect(resolved.model).toBe(unregisteredModelName);
      expect(resolved.generateContentConfig).toEqual({
        temperature: 0.99,
      });
    });
  });

  describe('custom overrides', () => {
    it('should apply custom overrides on top of defaults', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'test-alias': {
            modelConfig: {
              model: 'gemini-test',
              generateContentConfig: { temperature: 0.5 },
            },
          },
        },
        overrides: [
          {
            match: { model: 'test-alias' },
            modelConfig: { generateContentConfig: { temperature: 0.6 } },
          },
        ],
        customOverrides: [
          {
            match: { model: 'test-alias' },
            modelConfig: { generateContentConfig: { temperature: 0.7 } },
          },
        ],
      };
      const service = new ModelConfigService(config);
      const resolved = service.getResolvedConfig({ model: 'test-alias' });

      // Custom overrides should be appended to overrides, so they win
      expect(resolved.generateContentConfig.temperature).toBe(0.7);
    });
  });

  describe('retry behavior', () => {
    it('should apply retry-specific overrides when isRetry is true', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'test-model': {
            modelConfig: {
              model: 'gemini-test',
              generateContentConfig: {
                temperature: 0.5,
              },
            },
          },
        },
        overrides: [
          {
            match: { model: 'test-model', isRetry: true },
            modelConfig: {
              generateContentConfig: {
                temperature: 1.0,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);

      // Normal request
      const normal = service.getResolvedConfig({ model: 'test-model' });
      expect(normal.generateContentConfig.temperature).toBe(0.5);

      // Retry request
      const retry = service.getResolvedConfig({
        model: 'test-model',
        isRetry: true,
      });
      expect(retry.generateContentConfig.temperature).toBe(1.0);
    });

    it('should prioritize retry overrides over generic overrides', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'test-model': {
            modelConfig: {
              model: 'gemini-test',
              generateContentConfig: {
                temperature: 0.5,
              },
            },
          },
        },
        overrides: [
          // Generic override for this model
          {
            match: { model: 'test-model' },
            modelConfig: {
              generateContentConfig: {
                temperature: 0.7,
              },
            },
          },
          // Retry-specific override
          {
            match: { model: 'test-model', isRetry: true },
            modelConfig: {
              generateContentConfig: {
                temperature: 1.0,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);

      // Normal request - hits generic override
      const normal = service.getResolvedConfig({ model: 'test-model' });
      expect(normal.generateContentConfig.temperature).toBe(0.7);

      // Retry request - hits retry override (more specific)
      const retry = service.getResolvedConfig({
        model: 'test-model',
        isRetry: true,
      });
      expect(retry.generateContentConfig.temperature).toBe(1.0);
    });

    it('should apply overrides to parents in the alias hierarchy', () => {
      const config: ModelConfigServiceConfig = {
        aliases: {
          'base-alias': {
            modelConfig: {
              model: 'gemini-test',
              generateContentConfig: {
                temperature: 0.5,
              },
            },
          },
          'child-alias': {
            extends: 'base-alias',
            modelConfig: {
              generateContentConfig: {
                topP: 0.9,
              },
            },
          },
        },
        overrides: [
          {
            match: { model: 'base-alias', isRetry: true },
            modelConfig: {
              generateContentConfig: {
                temperature: 1.0,
              },
            },
          },
        ],
      };
      const service = new ModelConfigService(config);

      // Normal request
      const normal = service.getResolvedConfig({ model: 'child-alias' });
      expect(normal.generateContentConfig.temperature).toBe(0.5);

      // Retry request - should match override on parent
      const retry = service.getResolvedConfig({
        model: 'child-alias',
        isRetry: true,
      });
      expect(retry.generateContentConfig.temperature).toBe(1.0);
    });
  });

  describe('getAvailableModelOptions', () => {
    it('should filter out Pro models when hasAccessToProModel is false', () => {
      const config: ModelConfigServiceConfig = {
        modelDefinitions: {
          'gemini-3-pro': { isVisible: true, tier: 'pro' },
          'gemini-3-flash': { isVisible: true, tier: 'flash' },
        },
      };
      const service = new ModelConfigService(config);
      const options = service.getAvailableModelOptions({
        hasAccessToProModel: false,
      });

      expect(options.map((o) => o.modelId)).not.toContain('gemini-3-pro');
      expect(options.map((o) => o.modelId)).toContain('gemini-3-flash');
    });

    it('should include Pro models when hasAccessToProModel is true or undefined', () => {
      const config: ModelConfigServiceConfig = {
        modelDefinitions: {
          'gemini-3-pro': { isVisible: true, tier: 'pro' },
        },
      };
      const service = new ModelConfigService(config);

      const optionsWithTrue = service.getAvailableModelOptions({
        hasAccessToProModel: true,
      });
      expect(optionsWithTrue.map((o) => o.modelId)).toContain('gemini-3-pro');

      const optionsWithUndefined = service.getAvailableModelOptions({});
      expect(optionsWithUndefined.map((o) => o.modelId)).toContain(
        'gemini-3-pro',
      );
    });
  });
});
