process.env.OPENROUTER_API_KEY = 'or-secret-key-do-not-log';
process.env.OPENROUTER_BASE_URL = 'https://openrouter.test/api/v1';

const mockAxiosPost = jest.fn();
jest.mock('axios', () => ({
  post: (...args) => mockAxiosPost(...args),
}));

// No-op the rate limiter so tests don't burn 60s waiting for tokens.
jest.mock('../../src/utils/rateLimiter', () => ({
  createLimiter: () => ({ waitForToken: async () => true, snapshot: () => ({}) }),
  createCircuitBreaker: () => ({ call: async (fn) => fn(), snapshot: () => ({}) }),
}));

const adapter = require('../../src/services/adapters/openRouterAdapter');

beforeEach(() => {
  mockAxiosPost.mockReset();
  adapter.__internals.usage.requestsToday = 0;
});

describe('openRouterAdapter.configured', () => {
  test('reflects OPENROUTER_API_KEY presence', () => {
    expect(adapter.configured()).toBe(true);
    delete process.env.OPENROUTER_API_KEY;
    expect(adapter.configured()).toBe(false);
    process.env.OPENROUTER_API_KEY = 'or-secret-key-do-not-log';
  });
});

describe('openRouterAdapter.analyze', () => {
  test('successful primary call returns content + model + latency', async () => {
    mockAxiosPost.mockResolvedValueOnce({
      status: 200,
      data: { choices: [{ message: { content: '{"grade":"A","confidence":0.7}' } }] },
    });
    const result = await adapter.analyze('system', 'user');
    expect(result.response).toContain('grade');
    expect(result.modelUsed).toBe(adapter.__internals.PRIMARY_MODEL);
    expect(typeof result.latencyMs).toBe('number');
  });

  test('429 on primary triggers fallback model retry', async () => {
    mockAxiosPost
      .mockResolvedValueOnce({ status: 429, data: {} })
      .mockResolvedValueOnce({
        status: 200,
        data: { choices: [{ message: { content: '{"grade":"B"}' } }] },
      });
    const result = await adapter.analyze('s', 'u');
    expect(result).not.toBeNull();
    expect(result.modelUsed).toBe(adapter.__internals.FALLBACK_MODEL);
    expect(mockAxiosPost).toHaveBeenCalledTimes(2);
  });

  test('both models failing returns null', async () => {
    mockAxiosPost
      .mockResolvedValueOnce({ status: 500, data: {} })
      .mockResolvedValueOnce({ status: 500, data: {} });
    const result = await adapter.analyze('s', 'u');
    expect(result).toBeNull();
  });

  test('empty model response triggers fallback then null', async () => {
    mockAxiosPost
      .mockResolvedValueOnce({ status: 200, data: { choices: [] } })
      .mockResolvedValueOnce({ status: 200, data: { choices: [] } });
    const result = await adapter.analyze('s', 'u');
    expect(result).toBeNull();
  });

  test('returns null when unconfigured', async () => {
    delete process.env.OPENROUTER_API_KEY;
    expect(await adapter.analyze('s', 'u')).toBeNull();
    process.env.OPENROUTER_API_KEY = 'or-secret-key-do-not-log';
  });

  test('Bearer token is set on the request — never embedded in URL', async () => {
    mockAxiosPost.mockResolvedValueOnce({
      status: 200,
      data: { choices: [{ message: { content: '{}' } }] },
    });
    await adapter.analyze('s', 'u');
    const [url, body, opts] = mockAxiosPost.mock.calls[0];
    expect(url).not.toContain('or-secret-key-do-not-log');
    expect(JSON.stringify(body)).not.toContain('or-secret-key-do-not-log');
    expect(opts.headers.Authorization).toBe('Bearer or-secret-key-do-not-log');
  });

  test('scrubError does not surface header data', () => {
    const err = new Error('boom');
    err.code = 'ECONN';
    err.response = { status: 500, headers: { authorization: 'Bearer or-secret-key-do-not-log' } };
    const scrubbed = adapter.__internals.scrubError(err);
    expect(JSON.stringify(scrubbed)).not.toContain('or-secret-key-do-not-log');
    expect(scrubbed.status).toBe(500);
  });

  test('no `VYNDR` literal in prompt body or headers actually sent', async () => {
    mockAxiosPost.mockResolvedValueOnce({
      status: 200,
      data: { choices: [{ message: { content: '{}' } }] },
    });
    await adapter.analyze('system msg', 'user prompt');
    const [, body, opts] = mockAxiosPost.mock.calls[0];
    const wirePayload = JSON.stringify({ body, headers: opts.headers });
    // Brand-name check is uppercase only — vyndr.app as a referer domain
    // is acceptable (just a URL), but the uppercase brand string must not
    // be in any payload the provider sees.
    expect(wirePayload).not.toContain('VYNDR');
  });
});

describe('openRouterAdapter.getUsage', () => {
  test('reports requests today + remaining cap', async () => {
    mockAxiosPost.mockResolvedValue({
      status: 200,
      data: { choices: [{ message: { content: '{}' } }] },
    });
    await adapter.analyze('s', 'u');
    const usage = adapter.getUsage();
    expect(usage.requestsToday).toBeGreaterThanOrEqual(1);
    expect(usage.requestsRemaining).toBeLessThan(1000);
  });
});
