/**
 * bridge.ts — handle a single jambonz audio WebSocket call.
 *
 * Uses the same `RealtimeAgent` + `RealtimeSession` + `makeRestaurantTools`
 * pipeline as the Twilio bridge for full feature parity (place_order,
 * book_table, escalate_call, search_knowledge_base, list_menu). The only
 * difference is the transport layer (`JambonzRealtimeTransportLayer`).
 */
import { WebSocket } from 'ws';
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
import { childLogger } from '@server/logger';
const log = childLogger('engine.sip.bridge');

import {
  getOpenAIKey,
  lookupAgentBySessionId,
  fetchBranchContext,
  fetchAllBranches,
  resolveCallerContext,
  buildInstructions,
  saveTranscript,
  updateSessionStatus,
  makeTranscriptCollectorWithOfferTracking,
  TranscriptEntry,
} from '../voice-core';
import { JambonzRealtimeTransportLayer } from './transport';
// SIP-channel tool list: reuses every restaurant-domain tool from
// engine/twilio/tools.ts but swaps `escalate_call` for a jambonz-native
// transfer (no Twilio REST). See engine/sip/tools.ts.
import { makeSipRestaurantTools } from './tools';
import {
  DEFAULT_REALTIME_MODEL,
  DEFAULT_REALTIME_VOICE as DEFAULT_VOICE,
  VAD_DEFAULTS,
} from '@server/config/ai-defaults';

export async function handleSipCall(ws: WebSocket, sessionId: string): Promise<void> {
  log.info({ sessionId }, 'new connection');

  const agentConfig = await lookupAgentBySessionId(sessionId);
  if (!agentConfig) {
    log.warn({ sessionId }, 'no session found');
    ws.close();
    return;
  }

  const apiKey = await getOpenAIKey(agentConfig.restaurant_id);
  if (!apiKey) {
    log.error('no OpenAI API key found');
    ws.close();
    return;
  }

  const transcript: TranscriptEntry[] = [];
  let sessionClosed = false;
  const closeSession = async (status: 'completed' | 'failed' = 'completed') => {
    if (sessionClosed) return;
    sessionClosed = true;
    await saveTranscript(agentConfig.session_id, transcript);
    await updateSessionStatus(agentConfig.session_id, status);
    log.info({ sessionId, status }, 'session closed');
  };

  const model = agentConfig.realtime_model || DEFAULT_REALTIME_MODEL;
  const voice = agentConfig.voice_id || DEFAULT_VOICE;

  const needsBranchList = agentConfig.branch_routing_mode === 'ask_caller' || agentConfig.branch_routing_mode === 'geo_detect';
  const [branchCtx, callerCtx, branchList] = await Promise.all([
    fetchBranchContext(sessionId),
    resolveCallerContext(agentConfig.session_id),
    needsBranchList ? fetchAllBranches(agentConfig.restaurant_id) : Promise.resolve(null),
  ]);

  const transport = new JambonzRealtimeTransportLayer({ jambonzWebSocket: ws, useInsecureApiKey: true });

  const agent = new RealtimeAgent({
    name: 'RestaurantAgent',
    instructions: buildInstructions(agentConfig, branchCtx, callerCtx, branchList),
    tools: makeSipRestaurantTools({
      session_id: agentConfig.session_id,
      restaurant_id: agentConfig.restaurant_id,
      agent_id: agentConfig.agent_id,
      capabilities: agentConfig.capabilities,
      menu_category_ids: agentConfig.menu_category_ids,
      customer_id: callerCtx.customerId,
      caller_phone: callerCtx.callerPhone ?? null,
      branch_routing_mode: agentConfig.branch_routing_mode,
    }),
  });

  const vadThreshold = agentConfig.vad_threshold ?? VAD_DEFAULTS.threshold;
  const vadPrefixPaddingMs = agentConfig.vad_prefix_padding_ms ?? VAD_DEFAULTS.prefixPaddingMs;
  const vadSilenceDurationMs = agentConfig.vad_silence_duration_ms ?? VAD_DEFAULTS.silenceDurationMs;

  const session = new RealtimeSession(agent, {
    transport,
    model,
    apiKey,
    tracingDisabled: true,
    config: {
      audio: {
        output: { voice: voice as 'alloy' | 'echo' | 'shimmer' | 'ash' | 'ballad' | 'coral' | 'sage' | 'verse' },
        input: {
          turnDetection: {
            type: 'server_vad',
            threshold: vadThreshold,
            prefixPaddingMs: vadPrefixPaddingMs,
            silenceDurationMs: vadSilenceDurationMs,
            createResponse: true,
          },
        },
      },
    },
  });

  session.on('history_updated', makeTranscriptCollectorWithOfferTracking(transcript, agentConfig.session_id, callerCtx.activeOffers));

  ws.on('close', () => { closeSession('completed').catch(() => {}); });
  ws.on('error', (err) => {
    log.error({ err }, 'SIP WebSocket error');
    closeSession('failed').catch(() => {});
  });

  const CONNECT_TIMEOUT_MS = 15_000;
  try {
    await Promise.race([
      session.connect({ apiKey }),
      new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error('RealtimeSession connect timeout')), CONNECT_TIMEOUT_MS)
      ),
    ]);
    log.info({ model, voice }, 'RealtimeSession connected');
  } catch (err) {
    log.error({ err }, 'SIP failed to connect RealtimeSession');
    await closeSession('failed');
    ws.close();
  }
}
