/**
 * Shared Gemini client factory for Multideal AI agents.
 *
 * Uses the native @google/genai SDK with GOOGLE_API_KEY.
 * All LLM calls in this project must go through this module - never use
 * the OpenAI API or SDK, not even for compatibility shims.
 *
 * Primary/fallback pattern: every call tries the primary model first.
 * If it throws (rate-limit, model unavailable, etc.) it retries once with
 * the fallback model before propagating the error.
 *
 * Usage-returning variants (`generateTextWithUsage`, `generateTextWithImageWithUsage`)
 * expose token counts from `usageMetadata` for cost tracking. The original
 * `generateText` / `generateTextWithImage` functions remain as thin wrappers
 * so existing callers are unaffected.
 */

import { GoogleGenAI } from '@google/genai';
import { captureCaught } from '@/server/observability/capture.server';
import { requireConfiguredLlmModel } from './model-config.js';

export type GeminiClient = GoogleGenAI;

// ─── Usage types ──────────────────────────────────────────────────────────────

export interface GeminiUsage {
  promptTokens: number;
  completionTokens: number;
  totalTokens: number;
  /** The model that actually produced the response (may be fallback). */
  modelName: string;
  costUsd?: number | null;
}

export interface GeminiTextWithUsage {
  text: string;
  usage: GeminiUsage;
}

// ─── Client factory ───────────────────────────────────────────────────────────

export function createGeminiClient(apiKey: string): GeminiClient {
  return new GoogleGenAI({ apiKey });
}

// ─── Internal helper ──────────────────────────────────────────────────────────

interface GeminiResponseWithUsage {
  usageMetadata?: {
    promptTokenCount?: number;
    candidatesTokenCount?: number;
    totalTokenCount?: number;
  };
}

function extractUsage(response: GeminiResponseWithUsage, modelName: string): GeminiUsage {
  const meta = response.usageMetadata ?? {};
  return {
    promptTokens: meta.promptTokenCount ?? 0,
    completionTokens: meta.candidatesTokenCount ?? 0,
    totalTokens: meta.totalTokenCount ?? 0,
    modelName,
  };
}

// ─── With-usage variants ──────────────────────────────────────────────────────

/**
 * Generate a text response from Gemini, returning both text and usage metadata.
 * Tries `model` first; on error, retries with `fallbackModel`.
 */
export async function generateTextWithUsage(
  client: GeminiClient,
  prompt: string,
  model: string,
  fallbackModel: string,
): Promise<GeminiTextWithUsage> {
  model = requireConfiguredLlmModel(model, 'primary');
  fallbackModel = requireConfiguredLlmModel(fallbackModel, 'fallback');
  try {
    const response = await client.models.generateContent({ model, contents: prompt });
    return {
      text: response.text?.trim() ?? '',
      usage: extractUsage(response, model),
    };
  } catch (primaryErr) {
    if (fallbackModel === model) throw primaryErr;
    try {
      const response = await client.models.generateContent({
        model: fallbackModel,
        contents: prompt,
      });
      return {
        text: response.text?.trim() ?? '',
        usage: extractUsage(response, fallbackModel),
      };
    } catch (err) {
      captureCaught(err, { scope: 'server.ai.gemini', severity: 'warning' });
      throw primaryErr;
    }
  }
}

/**
 * Generate a response from Gemini given an image (as base64) and a text prompt,
 * returning both text and usage metadata.
 * Tries `model` first; on error, retries with `fallbackModel`.
 */
export async function generateTextWithImageWithUsage(
  client: GeminiClient,
  base64: string,
  mimeType: string,
  prompt: string,
  model: string,
  fallbackModel: string,
): Promise<GeminiTextWithUsage> {
  model = requireConfiguredLlmModel(model, 'primary');
  fallbackModel = requireConfiguredLlmModel(fallbackModel, 'fallback');
  const contents = [
    {
      role: 'user' as const,
      parts: [{ inlineData: { data: base64, mimeType } }, { text: prompt }],
    },
  ];
  try {
    const response = await client.models.generateContent({ model, contents });
    return {
      text: response.text?.trim() ?? '',
      usage: extractUsage(response, model),
    };
  } catch (primaryErr) {
    if (fallbackModel === model) throw primaryErr;
    try {
      const response = await client.models.generateContent({ model: fallbackModel, contents });
      return {
        text: response.text?.trim() ?? '',
        usage: extractUsage(response, fallbackModel),
      };
    } catch (err) {
      captureCaught(err, { scope: 'server.ai.gemini', severity: 'warning' });
      throw primaryErr;
    }
  }
}
