/**
 * Hybrid AI Router — Section A
 * Primary: Local Ollama (configurable endpoint)
 * Fallback: Anthropic Claude (claude-sonnet-4-20250514)
 *
 * All AI calls across the system route through this module.
 */

export type AIPriority = 1 | 2 | 3 | 4 | 5 | 6;
// 1=Interactive Console, 2=Breaking News, 3=Show Coverage, 4=Standard Auto, 5=Translation, 6=Classification

export interface HybridMessage {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

export interface HybridOptions {
  maxTokens?: number;
  temperature?: number;
  priority?: AIPriority;
  /** Background tasks use 120s timeout; interactive uses 60s */
  isBackground?: boolean;
  stream?: boolean;
}

export interface HybridResult {
  text: string;
  provider: 'ollama' | 'anthropic';
  model: string;
  latencyMs: number;
}

// ─── Config helpers ───────────────────────────────────────────────────────────

function getOllamaEndpoint(): string {
  return process.env.OLLAMA_ENDPOINT || '';
}

function getOllamaModel(taskType?: string): string {
  if (taskType === 'translation') return process.env.OLLAMA_MODEL_TRANSLATION || process.env.OLLAMA_MODEL_DEFAULT || '';
  if (taskType === 'classification') return process.env.OLLAMA_MODEL_BACKGROUND || process.env.OLLAMA_MODEL_DEFAULT || '';
  return process.env.OLLAMA_MODEL_DEFAULT || '';
}

function getAnthropicKey(): string {
  return process.env.ANTHROPIC_API_KEY || '';
}

function getOllamaHeaders(): Record<string, string> {
  const headers: Record<string, string> = { 'Content-Type': 'application/json' };
  const apiKey = process.env.OLLAMA_API_KEY;
  if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
  return headers;
}

// Primary model — fall back to latest Sonnet if unavailable
const ANTHROPIC_PRIMARY_MODEL = 'claude-sonnet-4-20250514';
const ANTHROPIC_FALLBACK_MODEL = 'claude-3-5-sonnet-20241022';

// ─── Ollama call ──────────────────────────────────────────────────────────────

async function callOllama(
  messages: HybridMessage[],
  model: string,
  endpoint: string,
  options: HybridOptions
): Promise<string> {
  const requestTimeout = options.isBackground ? 180_000 : 60_000;
  // 30s connection timeout (was 8s) — large models like llama3.1:70b
  // may need to swap into VRAM on AI_001/AI_002, which can exceed 10s
  // when another model is currently loaded.
  const connectionTimeout = 30_000;

  // Build Ollama OpenAI-compatible payload
  const payload = {
    model,
    messages,
    stream: false,
    options: {
      temperature: options.temperature ?? 0.7,
      num_predict: options.maxTokens ?? 2000,
    },
  };

  // Use AbortController for connection timeout
  const connectController = new AbortController();
  const connectTimer = setTimeout(() => connectController.abort(), connectionTimeout);

  let response: Response;
  try {
    response = await fetch(`${endpoint}/v1/chat/completions`, {
      method: 'POST',
      headers: getOllamaHeaders(),
      body: JSON.stringify(payload),
      signal: connectController.signal,
    });
  } finally {
    clearTimeout(connectTimer);
  }

  if (!response.ok) {
    throw new Error(`Ollama HTTP ${response.status}`);
  }

  // Request timeout for reading body
  const requestController = new AbortController();
  const requestTimer = setTimeout(() => requestController.abort(), requestTimeout);

  try {
    const data = await response.json();
    const text = data?.choices?.[0]?.message?.content || '';
    if (!text) throw new Error('Empty Ollama response');
    return text;
  } finally {
    clearTimeout(requestTimer);
  }
}

// ─── Anthropic call ───────────────────────────────────────────────────────────

async function callAnthropic(
  messages: HybridMessage[],
  options: HybridOptions,
  modelOverride?: string
): Promise<{ text: string; model: string }> {
  const apiKey = getAnthropicKey();
  if (!apiKey) throw new Error('ANTHROPIC_API_KEY not configured');

  const systemMsg = messages.find(m => m.role === 'system');
  const userMessages = messages.filter(m => m.role !== 'system');

  const model = modelOverride || ANTHROPIC_PRIMARY_MODEL;

  const payload: any = {
    model,
    max_tokens: options.maxTokens ?? 2000,
    messages: userMessages.map(m => ({ role: m.role, content: m.content })),
  };
  if (systemMsg) payload.system = systemMsg.content;
  if (options.temperature !== undefined) payload.temperature = options.temperature;

  let response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01',
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    // If primary model unavailable, try fallback
    if (response.status === 404 && model === ANTHROPIC_PRIMARY_MODEL) {
      return callAnthropic(messages, options, ANTHROPIC_FALLBACK_MODEL);
    }
    throw new Error(`Anthropic HTTP ${response.status}`);
  }

  const data = await response.json();
  const text = data?.content?.[0]?.text || '';
  if (!text) throw new Error('Empty Anthropic response');
  return { text, model };
}

// ─── Health log helper (fire-and-forget) ─────────────────────────────────────

async function logHealthEvent(event: {
  provider: 'ollama' | 'anthropic';
  success: boolean;
  latencyMs: number;
  model: string;
  priority: AIPriority;
  errorMessage?: string;
  isFailover?: boolean;
}) {
  try {
    // We call our own health log API to avoid circular Supabase imports in this module
    await fetch(`${process.env.NEXT_PUBLIC_SITE_URL || ''}/api/ai/health-log`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event),
    }).catch(() => {}); // truly fire-and-forget
  } catch {
    // never throw from health logging
  }
}

// ─── Main router ──────────────────────────────────────────────────────────────

export async function hybridAI(
  messages: HybridMessage[],
  options: HybridOptions = {},
  taskType?: string
): Promise<HybridResult> {
  const priority = options.priority ?? 4;
  const endpoint = getOllamaEndpoint();
  const ollamaModel = getOllamaModel(taskType);

  if (!endpoint || !ollamaModel) {
    throw new Error('Ollama not configured (OLLAMA_ENDPOINT or model missing) — refusing to fall back to paid API');
  }

  // Retry-with-backoff against Ollama. Per the no-paid-AI policy we never
  // fall back to Anthropic, so the caller gets a clear error and is expected
  // to surface its own graceful fallback (e.g. non-AI default).
  const RETRY_DELAYS_MS = [0, 3000, 12000];
  let lastErr: any = null;
  for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
    if (RETRY_DELAYS_MS[i] > 0) await new Promise((r) => setTimeout(r, RETRY_DELAYS_MS[i]));
    const start = Date.now();
    try {
      const text = await callOllama(messages, ollamaModel, endpoint, options);
      const latencyMs = Date.now() - start;
      logHealthEvent({ provider: 'ollama', success: true, latencyMs, model: ollamaModel, priority });
      return { text, provider: 'ollama', model: ollamaModel, latencyMs };
    } catch (err: any) {
      lastErr = err;
      const latencyMs = Date.now() - start;
      logHealthEvent({
        provider: 'ollama', success: false, latencyMs, model: ollamaModel, priority,
        errorMessage: err?.message,
      });
      // Retry-eligible if message looks like 503/busy/timeout; otherwise bail.
      if (!/503|busy|pending|queue|timeout|empty|abort/i.test(String(err?.message || ''))) break;
    }
  }

  throw lastErr || new Error('Ollama failed after retries');
}

// ─── Streaming variant (for AI Console) ──────────────────────────────────────

export async function hybridAIStream(
  messages: HybridMessage[],
  options: HybridOptions = {}
): Promise<{ stream: ReadableStream; provider: 'ollama' | 'anthropic'; model: string }> {
  const endpoint = getOllamaEndpoint();
  const ollamaModel = getOllamaModel();
  const priority = options.priority ?? 1;

  // Try Ollama streaming first
  if (endpoint && ollamaModel) {
    const connectController = new AbortController();
    const connectTimer = setTimeout(() => connectController.abort(), 8_000);

    try {
      const payload = {
        model: ollamaModel,
        messages,
        stream: true,
        options: { temperature: options.temperature ?? 0.7, num_predict: options.maxTokens ?? 2000 },
      };

      let response = await fetch(`${endpoint}/v1/chat/completions`, {
        method: 'POST',
        headers: getOllamaHeaders(),
        body: JSON.stringify(payload),
        signal: connectController.signal,
      });

      clearTimeout(connectTimer);

      if (response.ok && response.body) {
        logHealthEvent({ provider: 'ollama', success: true, latencyMs: 0, model: ollamaModel, priority });
        return { stream: response.body, provider: 'ollama', model: ollamaModel };
      }
    } catch {
      clearTimeout(connectTimer);
      logHealthEvent({ provider: 'ollama', success: false, latencyMs: 0, model: ollamaModel, priority, isFailover: true });
    }
  }

  // No-paid-AI policy: no Anthropic fallback. If Ollama streaming failed,
  // surface that clearly so the caller can degrade gracefully.
  throw new Error('Ollama streaming unavailable — no paid-AI fallback per policy');
}

// ─── Ollama model list ────────────────────────────────────────────────────────

export async function getOllamaModels(): Promise<string[]> {
  const endpoint = getOllamaEndpoint();
  if (!endpoint) return [];
  try {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 8_000);
    let response = await fetch(`${endpoint}/api/tags`, {
      headers: getOllamaHeaders(),
      signal: controller.signal,
    });
    clearTimeout(timer);
    if (!response.ok) return [];
    const data = await response.json();
    return (data?.models || []).map((m: any) => m.name as string).filter(Boolean);
  } catch {
    return [];
  }
}

// ─── Ollama ping ──────────────────────────────────────────────────────────────

export async function pingOllama(): Promise<{ online: boolean; latencyMs: number; models: string[] }> {
  const endpoint = getOllamaEndpoint();
  if (!endpoint) return { online: false, latencyMs: 0, models: [] };
  const start = Date.now();
  try {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 8_000);
    let response = await fetch(`${endpoint}/api/tags`, {
      headers: getOllamaHeaders(),
      signal: controller.signal,
    });
    clearTimeout(timer);
    const latencyMs = Date.now() - start;
    if (!response.ok) return { online: false, latencyMs, models: [] };
    const data = await response.json();
    const models = (data?.models || []).map((m: any) => m.name as string).filter(Boolean);
    return { online: true, latencyMs, models };
  } catch {
    return { online: false, latencyMs: Date.now() - start, models: [] };
  }
}
