/**
 * MockAdapter — verify the test-double itself behaves predictably
 * so downstream tests using it don't get false negatives.
 */

import { describe, test, expect } from '@jest/globals';
import MockAdapter from '../MockAdapter.js';
import { ModelId } from '../constants.js';

const ANY = Buffer.from([0x89, 0x50, 0x4E, 0x47]); // bytes don't matter; mock ignores them

describe('MockAdapter — lookup map', () => {
  test('returns the configured coord for an intent substring match', async () => {
    const m = new MockAdapter({ responses: { 'save button': { x: 100, y: 200 } } });
    const r = await m.ground({ screenshot: ANY, intent: 'click the Save Button now' });
    expect(r.coords).toEqual({ x: 100, y: 200 });
    expect(r.answer).toBe('x=100, y=200');
    expect(r.modelId).toBe(ModelId.MOCK);
  });

  test('falls back to deterministic hash when no map entry matches', async () => {
    const m = new MockAdapter();
    const r1 = await m.ground({ screenshot: ANY, intent: 'click foo', imageSize: { width: 1920, height: 1080 } });
    const r2 = await m.ground({ screenshot: ANY, intent: 'click foo', imageSize: { width: 1920, height: 1080 } });
    expect(r1.coords).toEqual(r2.coords); // determinism
    expect(r1.coords.x).toBeGreaterThanOrEqual(0);
    expect(r1.coords.x).toBeLessThan(1920);
    expect(r1.coords.y).toBeGreaterThanOrEqual(0);
    expect(r1.coords.y).toBeLessThan(1080);
  });

  test('force_coords knob overrides everything', async () => {
    const m = new MockAdapter({ responses: { 'foo': { x: 999, y: 999 } } });
    const r = await m.ground({
      screenshot: ANY, intent: 'foo',
      knobs: { force_coords: 'x=50, y=60' },
    });
    expect(r.coords).toEqual({ x: 50, y: 60 });
  });
});

describe('MockAdapter — telemetry', () => {
  test('logs every call (introspection point for tests)', async () => {
    const m = new MockAdapter();
    await m.ground({ screenshot: ANY, intent: 'a' });
    await m.ground({ screenshot: ANY, intent: 'b' });
    expect(m.callLog).toHaveLength(2);
    expect(m.callLog[0].intent).toBe('a');
    expect(m.callLog[1].intent).toBe('b');
  });

  test('simulated latency + tokens match config', async () => {
    const m = new MockAdapter({ latencyMs: 42, tokens: 9 });
    const r = await m.ground({ screenshot: ANY, intent: 'x' });
    expect(r.elapsed_ms).toBe(42);
    expect(r.usage.output_tokens).toBe(9);
  });
});

describe('MockAdapter — failure injection', () => {
  test('failWith rejects every call (for error-path tests)', async () => {
    const boom = new Error('simulated upstream error');
    const m = new MockAdapter({ failWith: boom });
    await expect(m.ground({ screenshot: ANY, intent: 'x' })).rejects.toThrow(/simulated/);
  });
});
