import { getLlmClientConfig } from '@server/services/ai-providers.service';

import { childLogger } from '@server/logger';
const log = childLogger('svc.voice.llm');

export interface TranscriptEntry {
  role: 'system' | 'user' | 'assistant';
  text: string;
  ts: string;
}

export interface LlmResponse {
  text: string;
  shouldEscalate: boolean;
}

const RETRY_DELAY_MS = 1200;
const PLEASE_REPEAT = 'I am sorry, I did not quite catch that. Could you please repeat what you said?';
const NO_MODEL_MSG = 'No language model is configured for this agent. Please contact the restaurant directly or ask to speak with a staff member.';
const NO_KEY_MSG = 'The AI service is currently unavailable. Please try again in a moment or ask to speak with a staff member.';

async function callLlmApi(
  config: { apiBaseUrl: string; authHeaderName: string; authHeaderValue: string | null; modelId: string },
  messages: { role: string; content: string }[]
): Promise<Response> {
  return fetch(`${config.apiBaseUrl}/chat/completions`, {
    method: 'POST',
    headers: {
      [config.authHeaderName]: config.authHeaderValue || '',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: config.modelId,
      messages,
      max_tokens: 300,
      temperature: 0.7,
    }),
  });
}

export async function generateResponse(
  llmModelId: string | null,
  systemPrompt: string,
  transcript: TranscriptEntry[],
  callerInput: string,
  restaurantId?: string | null
): Promise<LlmResponse> {
  if (!llmModelId) {
    return { text: NO_MODEL_MSG, shouldEscalate: true };
  }

  const config = await getLlmClientConfig(llmModelId, restaurantId);
  if (!config.apiKey) {
    return { text: NO_KEY_MSG, shouldEscalate: false };
  }

  const messages = buildMessages(systemPrompt, transcript, callerInput);

  const callConfig = {
    apiBaseUrl: config.apiBaseUrl,
    authHeaderName: config.authHeaderName,
    authHeaderValue: config.authHeaderValue ?? null,
    modelId: config.modelId,
  };

  let res: Response;
  try {
    res = await callLlmApi(callConfig, messages);
  } catch (networkErr: unknown) {
    const msg = networkErr instanceof Error ? networkErr.message : String(networkErr);
    log.error({ modelId: config.modelId, msg }, 'LLM network error on first attempt');
    await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
    try {
      res = await callLlmApi(callConfig, messages);
    } catch (retryErr: unknown) {
      const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
      log.error({ modelId: config.modelId, msg: retryMsg }, 'LLM network error on retry');
      return { text: PLEASE_REPEAT, shouldEscalate: false };
    }
  }

  if (!res.ok) {
    const errText = await res.text();
    log.error({ status: res.status, modelId: config.modelId, errText }, 'LLM API error on first attempt');

    if (res.status === 401 || res.status === 403) {
      return { text: NO_KEY_MSG, shouldEscalate: false };
    }

    if (res.status >= 500 || res.status === 429) {
      await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
      let retryRes: Response;
      try {
        retryRes = await callLlmApi(callConfig, messages);
      } catch (retryNetErr: unknown) {
        const msg = retryNetErr instanceof Error ? retryNetErr.message : String(retryNetErr);
        log.error({ initialStatus: res.status, modelId: config.modelId, msg }, 'LLM network error on retry');
        return { text: PLEASE_REPEAT, shouldEscalate: false };
      }

      if (!retryRes.ok) {
        const retryErrText = await retryRes.text();
        log.error({ status: retryRes.status, modelId: config.modelId, errText: retryErrText }, 'LLM API error on retry');
        return { text: PLEASE_REPEAT, shouldEscalate: false };
      }

      return parseResponse(await retryRes.json() as { choices?: { message?: { content?: string } }[] });
    }

    return { text: PLEASE_REPEAT, shouldEscalate: false };
  }

  return parseResponse(await res.json() as { choices?: { message?: { content?: string } }[] });
}

function parseResponse(data: { choices?: { message?: { content?: string } }[] }): LlmResponse {
  const responseText = data.choices?.[0]?.message?.content?.trim() || '';
  if (!responseText) {
    return { text: PLEASE_REPEAT, shouldEscalate: false };
  }
  // Belt-and-suspenders escalation check for the fallback LLM path only.
  // The primary Realtime path uses a proper `escalate_call` tool call instead.
  // Pattern requires an explicit intent phrase ("speak to / talk to / connect to /
  // transfer to") paired with a human referent — avoids false positives like
  // "I'll connect you to the right menu section".
  const shouldEscalate = /\b(speak(?:ing)? to|talk(?:ing)? to|connect(?:ing)? (?:you )?to|transfer(?:ring)? (?:you )?to)\b.{0,40}\b(a person|a human|a manager|a supervisor|someone from our team|a staff member|our staff|our team)\b/i.test(responseText);
  return { text: responseText, shouldEscalate };
}

function buildMessages(
  systemPrompt: string,
  transcript: TranscriptEntry[],
  callerInput: string
): { role: string; content: string }[] {
  const msgs: { role: string; content: string }[] = [
    { role: 'system', content: systemPrompt },
  ];

  for (const entry of transcript) {
    if (entry.role === 'system') continue;
    msgs.push({
      role: entry.role === 'user' ? 'user' : 'assistant',
      content: entry.text,
    });
  }

  msgs.push({ role: 'user', content: callerInput });
  return msgs;
}
