import { describe, expect, test } from "bun:test";
import { FailoverVoiceProvider } from "./failover-voice-provider.ts";
import { FakeVoiceProvider } from "./fake-voice-provider.ts";
import type { VoiceAgentConfig } from "./config.ts";
import { createVoiceProvider } from "./provider-factory.ts";
import type {
  VoiceProvider,
  VoiceProviderConnectInput,
  VoiceProviderKind,
  VoiceProviderSession,
} from "./types.ts";

describe("FailoverVoiceProvider", () => {
  test("uses Grok adapter when OpenAI connect fails", async () => {
    const provider = new FailoverVoiceProvider(
      new ThrowingVoiceProvider("openai_realtime"),
      [new FakeVoiceProvider("grok_voice")],
    );

    const session = await provider.connect(connectInput());

    expect(session.provider).toBe("grok_voice");
  });

  test("reports all provider failures when no fallback connects", async () => {
    const provider = new FailoverVoiceProvider(
      new ThrowingVoiceProvider("openai_realtime", "primary down"),
      [new ThrowingVoiceProvider("grok_voice", "fallback down")],
    );

    await expect(provider.connect(connectInput())).rejects.toThrow(
      "openai_realtime: primary down; grok_voice: fallback down",
    );
  });
});

describe("createVoiceProvider", () => {
  test("builds OpenAI to Grok failover only when explicitly enabled and PHI approved", () => {
    const provider = createVoiceProvider("openai_realtime", config());

    expect(provider).toBeInstanceOf(FailoverVoiceProvider);
  });

  test("keeps OpenAI primary-only when Grok PHI approval is absent", () => {
    const provider = createVoiceProvider("openai_realtime", {
      ...config(),
      xai: { ...config().xai, phiAllowed: false },
    });

    expect(provider).not.toBeInstanceOf(FailoverVoiceProvider);
  });
});

class ThrowingVoiceProvider implements VoiceProvider {
  constructor(
    readonly kind: VoiceProviderKind,
    private readonly message = "connect failed",
  ) {}

  async connect(_input: VoiceProviderConnectInput): Promise<VoiceProviderSession> {
    throw new Error(this.message);
  }
}

function connectInput(): VoiceProviderConnectInput {
  return {
    callId: "call-1",
    instructions: "test",
    inputFormat: { codec: "pcm16", sampleRateHz: 24000, channels: 1 },
    outputFormat: { codec: "pcm16", sampleRateHz: 24000, channels: 1 },
    tools: [],
    events: {},
  };
}

function config(): VoiceAgentConfig {
  const endpoint = (name: string) => ({
    name,
    mcpUrl: `https://example.com/${name}/mcp`,
    bearerToken: "token",
    requiresPhiTransport: true,
  });
  return {
    defaultVoiceProvider: "openai_realtime",
    voiceProviderFailover: true,
    openai: {
      apiKey: "openai",
      model: "gpt-realtime",
      realtimeUrl: "wss://api.openai.com/v1/realtime",
      voice: "alloy",
    },
    xai: {
      apiKey: "xai",
      model: "grok-voice-think-fast-1.0",
      realtimeUrl: "wss://api.x.ai/v1/realtime",
      voice: "eve",
      phiAllowed: true,
    },
    audio: {
      ringCentralFormat: { codec: "opus", sampleRateHz: 16000, channels: 1 },
      providerFormat: { codec: "pcm16", sampleRateHz: 16000, channels: 1 },
    },
    mcp: {
      advancedmd: endpoint("advancedmd"),
      ringcentral: endpoint("ringcentral"),
      ringcentralAdmin: endpoint("ringcentral-admin"),
    },
    scheduling: {
      maxConcurrentTransactions: 1,
      amdLookupTimeoutMs: 6000,
    },
  };
}
