const assert = require('assert');

const {
  buildCodexModelEffortMap,
  getEffortForModel,
  injectReasoningEffortIntoBody,
} = require('../../../dist/cliproxy/codex-reasoning-proxy');

describe('Codex Reasoning Proxy', () => {
  describe('buildCodexModelEffortMap', () => {
    it('maps tier models to expected efforts', () => {
      const map = buildCodexModelEffortMap({
        defaultModel: 'gpt-5.2-codex',
        opusModel: 'gpt-5.2-codex',
        sonnetModel: 'gpt-5.2-codex-high',
        haikuModel: 'gpt-5.2-codex-mini',
      });

      assert.strictEqual(map.get('gpt-5.2-codex'), 'xhigh');
      assert.strictEqual(map.get('gpt-5.2-codex-high'), 'high');
      assert.strictEqual(map.get('gpt-5.2-codex-mini'), 'medium');
    });

    it('uses the lowest effort when the same model appears in multiple tiers', () => {
      const map = buildCodexModelEffortMap({
        opusModel: 'same',
        sonnetModel: 'same',
        haikuModel: 'same',
      });

      assert.strictEqual(map.get('same'), 'medium');
    });

    it('returns empty map when all models undefined', () => {
      const map = buildCodexModelEffortMap({});
      assert.strictEqual(map.size, 0);
    });

    it('ignores empty string models', () => {
      const map = buildCodexModelEffortMap({ defaultModel: '', opusModel: '  ' });
      assert.strictEqual(map.size, 0);
    });
  });

  describe('getEffortForModel', () => {
    it('defaults to medium for unknown model', () => {
      const map = buildCodexModelEffortMap({ opusModel: 'm1' });
      assert.strictEqual(getEffortForModel('unknown', map, 'medium'), 'medium');
    });

    it('handles null model', () => {
      const map = buildCodexModelEffortMap({ opusModel: 'm1' });
      assert.strictEqual(getEffortForModel(null, map, 'high'), 'high');
    });
  });

  describe('injectReasoningEffortIntoBody', () => {
    it('overrides reasoning.effort on object bodies', () => {
      const out = injectReasoningEffortIntoBody(
        { model: 'm1', reasoning: { effort: 'xhigh' } },
        'high'
      );
      assert.deepStrictEqual(out, { model: 'm1', reasoning: { effort: 'high' } });
    });

    it('leaves non-object bodies unchanged', () => {
      assert.strictEqual(injectReasoningEffortIntoBody('x', 'medium'), 'x');
    });

    it('leaves array bodies unchanged (not treated as record)', () => {
      const arr = [1, 2, 3];
      assert.deepStrictEqual(injectReasoningEffortIntoBody(arr, 'high'), arr);
    });
  });

  describe('model suffix aliasing', () => {
    it('lets callers use model-id suffixes without changing upstream model ids', () => {
      // We can't call the full proxy in unit tests, but we can validate the intent:
      // - injected effort is based on the suffix
      // - callers should set request.model to the base model upstream (proxy does this)
      const out = injectReasoningEffortIntoBody(
        { model: 'gpt-5.2-codex', reasoning: { effort: 'medium' } },
        'xhigh'
      );
      assert.deepStrictEqual(out, {
        model: 'gpt-5.2-codex',
        reasoning: { effort: 'xhigh' },
      });
    });
  });

  describe('stripPathPrefix (remote mode)', () => {
    // Tests the path prefix stripping logic used in remote proxy mode
    // Remote CLIProxyAPI expects /v1/messages, but Claude sends /api/provider/codex/v1/messages

    /**
     * Updated to match the version in codex-reasoning-proxy.ts with boundary check.
     * Only strips if prefix matches a complete path segment (not partial like /codex matching /codextra)
     */
    function stripPathPrefix(path, prefix) {
      if (
        prefix &&
        path.startsWith(prefix) &&
        (path.length === prefix.length || path[prefix.length] === '/')
      ) {
        let stripped = path.slice(prefix.length);
        // Normalize: collapse any leading slashes to single slash and ensure path starts with '/'
        stripped = stripped.replace(/^\/+/, '/') || '/';
        if (!stripped.startsWith('/')) {
          stripped = '/' + stripped;
        }
        return stripped;
      }
      return path;
    }

    it('strips /api/provider/codex prefix for remote mode', () => {
      const result = stripPathPrefix('/api/provider/codex/v1/messages', '/api/provider/codex');
      assert.strictEqual(result, '/v1/messages');
    });

    it('returns root path when prefix equals full path', () => {
      const result = stripPathPrefix('/api/provider/codex', '/api/provider/codex');
      assert.strictEqual(result, '/');
    });

    it('leaves path unchanged when prefix is undefined', () => {
      const result = stripPathPrefix('/v1/messages', undefined);
      assert.strictEqual(result, '/v1/messages');
    });

    it('leaves path unchanged when prefix does not match', () => {
      const result = stripPathPrefix('/v1/messages', '/api/provider/codex');
      assert.strictEqual(result, '/v1/messages');
    });

    it('handles empty prefix', () => {
      const result = stripPathPrefix('/v1/messages', '');
      assert.strictEqual(result, '/v1/messages');
    });

    it('handles various provider prefixes', () => {
      // Gemini
      assert.strictEqual(
        stripPathPrefix('/api/provider/gemini/v1/chat', '/api/provider/gemini'),
        '/v1/chat'
      );
      // Agy
      assert.strictEqual(
        stripPathPrefix('/api/provider/agy/v1/messages', '/api/provider/agy'),
        '/v1/messages'
      );
    });

    it('preserves query strings after stripping', () => {
      const result = stripPathPrefix('/api/provider/codex/v1/messages?stream=true', '/api/provider/codex');
      assert.strictEqual(result, '/v1/messages?stream=true');
    });

    // Edge cases for path normalization
    it('collapses double slashes after stripping', () => {
      // e.g., '/api/provider/codex//v1/messages' → '/v1/messages'
      const result = stripPathPrefix('/api/provider/codex//v1/messages', '/api/provider/codex');
      assert.strictEqual(result, '/v1/messages');
    });

    it('handles multiple leading slashes after stripping', () => {
      // e.g., '/api/provider/codex///v1' → '/v1'
      const result = stripPathPrefix('/api/provider/codex///v1', '/api/provider/codex');
      assert.strictEqual(result, '/v1');
    });

    it('adds leading slash if missing after strip', () => {
      const result = stripPathPrefix('/prefix/suffix', '/prefix');
      assert.strictEqual(result, '/suffix');
    });

    // Boundary check tests - prevent partial segment matching
    it('does NOT strip partial path segment matches', () => {
      // /codex should NOT match /codextra
      const result = stripPathPrefix('/api/provider/codextra/v1/messages', '/api/provider/codex');
      assert.strictEqual(result, '/api/provider/codextra/v1/messages');
    });

    it('does NOT strip when prefix matches but next char is not slash', () => {
      // /api should NOT match /api-v2
      const result = stripPathPrefix('/api-v2/messages', '/api');
      assert.strictEqual(result, '/api-v2/messages');
    });

    it('strips when prefix matches exactly with slash boundary', () => {
      // /api/provider/codex should match /api/provider/codex/v1
      const result = stripPathPrefix('/api/provider/codex/v1', '/api/provider/codex');
      assert.strictEqual(result, '/v1');
    });
  });
});
