/**
 * Unit tests for `{env:VAR}` substitution in the MCP config loader.
 * Pure — no live MCP server. Verifies secrets resolve from process.env
 * (so real keys live in `.env`, not the gitignored config file).
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { substituteEnvVars } from '../mcp-config.js';

// Minimal FastifyBaseLogger stub — only .warn is exercised here.
function fakeLog() {
  const warnings: string[] = [];
  const log = {
    warn: (msg: unknown) => {
      warnings.push(typeof msg === 'string' ? msg : JSON.stringify(msg));
    },
  };
  return { log: log as never, warnings };
}

describe('substituteEnvVars', () => {
  const SAVED = process.env.MCP_TEST_SECRET;
  beforeEach(() => {
    process.env.MCP_TEST_SECRET = 'resolved-value';
  });
  afterEach(() => {
    if (SAVED === undefined) delete process.env.MCP_TEST_SECRET;
    else process.env.MCP_TEST_SECRET = SAVED;
    delete process.env.MCP_TEST_MISSING;
  });

  it('replaces a {env:VAR} reference in a string value', () => {
    const { log } = fakeLog();
    expect(substituteEnvVars('{env:MCP_TEST_SECRET}', log)).toBe('resolved-value');
  });

  it('substitutes inside nested objects and arrays', () => {
    const { log } = fakeLog();
    const out = substituteEnvVars(
      {
        headers: { CONTEXT7_API_KEY: '{env:MCP_TEST_SECRET}' },
        args: ['--token', '{env:MCP_TEST_SECRET}'],
      },
      log,
    );
    expect(out).toEqual({
      headers: { CONTEXT7_API_KEY: 'resolved-value' },
      args: ['--token', 'resolved-value'],
    });
  });

  it('leaves object keys untouched, only transforms values', () => {
    const { log } = fakeLog();
    const out = substituteEnvVars({ '{env:MCP_TEST_SECRET}': 'literal' }, log) as Record<string, string>;
    expect(Object.keys(out)).toEqual(['{env:MCP_TEST_SECRET}']);
  });

  it('resolves an unset var to empty string and warns', () => {
    const { log, warnings } = fakeLog();
    expect(substituteEnvVars('{env:MCP_TEST_MISSING}', log)).toBe('');
    expect(warnings.some((w) => w.includes('MCP_TEST_MISSING'))).toBe(true);
  });

  it('passes non-string scalars through unchanged', () => {
    const { log } = fakeLog();
    expect(substituteEnvVars(true, log)).toBe(true);
    expect(substituteEnvVars(42, log)).toBe(42);
    expect(substituteEnvVars(null, log)).toBe(null);
  });

  it('leaves strings without a reference unchanged', () => {
    const { log } = fakeLog();
    expect(substituteEnvVars('https://mcp.context7.com/mcp', log)).toBe('https://mcp.context7.com/mcp');
  });

  it('resolves multiple references in one string (global flag)', () => {
    const { log } = fakeLog();
    expect(substituteEnvVars('{env:MCP_TEST_SECRET}/{env:MCP_TEST_SECRET}', log)).toBe(
      'resolved-value/resolved-value',
    );
  });

  it('passes an empty string through unchanged', () => {
    const { log } = fakeLog();
    expect(substituteEnvVars('', log)).toBe('');
  });

  it('collects unset var names into the optional collector set', () => {
    const { log } = fakeLog();
    const unset = new Set<string>();
    substituteEnvVars({ url: '{env:MCP_TEST_MISSING}', headers: { k: '{env:MCP_TEST_SECRET}' } }, log, unset);
    expect([...unset]).toEqual(['MCP_TEST_MISSING']);
  });
});
