import { describe, it, expect } from 'vitest';
import { OpenAIRealtime2Adapter } from '../src/providers/openai-realtime-2';
import type { OpenAIRealtimeOptions } from '../src/providers/openai-realtime';

/**
 * Build the GA (`gpt-realtime-2`) `session.update` payload directly via the
 * adapter's private builder. The `private` keyword in TypeScript is a
 * compile-time-only annotation — at runtime the method is a plain property,
 * so an `as` cast reaches it without spinning up a WebSocket. This keeps the
 * assertion authentic: it exercises the real builder, not a re-implementation.
 */
function buildGA(opts: OpenAIRealtimeOptions): Record<string, unknown> {
  const adapter = new OpenAIRealtime2Adapter(
    'sk_test',
    'gpt-realtime-2',
    'alloy',
    'You are helpful.',
    [],
    undefined,
    opts,
  );
  return (adapter as unknown as { buildGASessionConfig(): Record<string, unknown> })
    .buildGASessionConfig();
}

function audioInput(config: Record<string, unknown>): Record<string, unknown> {
  return (config.audio as { input: Record<string, unknown> }).input;
}

describe('[unit] OpenAIRealtime2Adapter GA transcription language', () => {
  function transcription(config: Record<string, unknown>): Record<string, unknown> {
    return audioInput(config).transcription as Record<string, unknown>;
  }

  it('pins audio.input.transcription.language when transcriptionLanguage is set', () => {
    const t = transcription(buildGA({ transcriptionLanguage: 'it' }));
    expect(t.language).toBe('it');
    // The model is still emitted (default whisper-1) — language is additive.
    expect(typeof t.model).toBe('string');
  });

  it('OMITS the language key entirely when unset (Whisper auto-detect, today behavior)', () => {
    const t = transcription(buildGA({}));
    expect('language' in t).toBe(false);
  });
});

describe('[unit] OpenAIRealtime2Adapter GA noise reduction', () => {
  it("nests noise_reduction under session.audio.input when set to 'far_field'", () => {
    const config = buildGA({ noiseReduction: 'far_field' });
    expect(audioInput(config).noise_reduction).toEqual({ type: 'far_field' });
  });

  it("accepts 'near_field'", () => {
    const config = buildGA({ noiseReduction: 'near_field' });
    expect(audioInput(config).noise_reduction).toEqual({ type: 'near_field' });
  });

  it('OMITS the noise-reduction key entirely when unset (today behavior)', () => {
    const config = buildGA({});
    expect('noise_reduction' in audioInput(config)).toBe(false);
  });
});

describe('[unit] OpenAIRealtime2Adapter GA turn detection', () => {
  it('respects an explicit server_vad threshold + silence over the defaults', () => {
    const config = buildGA({
      turnDetection: { type: 'server_vad', threshold: 0.6, silenceDurationMs: 500 },
    });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.type).toBe('server_vad');
    expect(td.threshold).toBe(0.6);
    expect(td.silence_duration_ms).toBe(500);
    // Unset prefix falls back to the adapter default.
    expect(td.prefix_padding_ms).toBe(300);
    // Server-managed default (issue #154): the server owns response creation
    // AND the barge-in cancel signal — both flags true.
    expect(td.create_response).toBe(true);
    expect(td.interrupt_response).toBe(true);
  });

  it('falls back each unset server_vad field to the current adapter default', () => {
    const config = buildGA({ turnDetection: { type: 'server_vad', threshold: 0.7 } });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.threshold).toBe(0.7);
    expect(td.prefix_padding_ms).toBe(300);
    expect(td.silence_duration_ms).toBe(300);
  });

  it("emits semantic_vad with eagerness and NO threshold/prefix/silence", () => {
    const config = buildGA({ turnDetection: { type: 'semantic_vad', eagerness: 'low' } });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.type).toBe('semantic_vad');
    expect(td.eagerness).toBe('low');
    expect('threshold' in td).toBe(false);
    expect('prefix_padding_ms' in td).toBe(false);
    expect('silence_duration_ms' in td).toBe(false);
    // Server-managed response-gating keys still emitted on semantic_vad.
    expect(td.create_response).toBe(true);
    expect(td.interrupt_response).toBe(true);
  });

  it('omits semantic_vad eagerness when not provided (server default applies)', () => {
    const config = buildGA({ turnDetection: { type: 'semantic_vad' } });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.type).toBe('semantic_vad');
    expect('eagerness' in td).toBe(false);
  });
});

describe('[unit] OpenAIRealtime2Adapter GA defaults unchanged (regression guard)', () => {
  it('produces the server-managed turn_detection literal and no noise key when no knobs are set', () => {
    const config = buildGA({});
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td).toEqual({
      type: 'server_vad',
      threshold: 0.5,
      prefix_padding_ms: 300,
      silence_duration_ms: 300,
      // Server-managed default (issue #154): the server auto-creates the
      // response on commit (create_response) AND owns the barge-in cancel
      // (interrupt_response). The e2e model is not gated on the Whisper
      // transcript and the client only sends sendClear + truncate on barge-in.
      create_response: true,
      interrupt_response: true,
    });
    expect('noise_reduction' in audioInput(config)).toBe(false);
  });

  it('legacy opt-out (gateResponseOnTranscript true) flips BOTH keys to false (client-managed)', () => {
    const config = buildGA({ gateResponseOnTranscript: true });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    // Client-managed: Patter drives response.create after the hallucination
    // filter and response.cancel on barge-in itself.
    expect(td.create_response).toBe(false);
    expect(td.interrupt_response).toBe(false);
  });

  it('default (gateResponseOnTranscript false explicit) sets BOTH keys true (server-managed)', () => {
    const config = buildGA({ gateResponseOnTranscript: false });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.create_response).toBe(true);
    expect(td.interrupt_response).toBe(true);
  });

  it('honors a pre-existing silenceDurationMs option as the server_vad default', () => {
    const config = buildGA({ silenceDurationMs: 700 });
    const td = audioInput(config).turn_detection as Record<string, unknown>;
    expect(td.silence_duration_ms).toBe(700);
  });
});
