/**
 * Pi test-completion — single-shot, non-streaming completion call.
 *
 * Iteration 1 of the pi harness: just enough to verify the saved sub-provider
 * + credentials actually reach an LLM and return text. Replaces the full
 * pi-ai streaming stack until we vendor it alongside the agent loop.
 *
 * Supported API flavors:
 *   - openai-completions  → POST {baseUrl}/chat/completions
 *   - anthropic-messages  → POST {baseUrl}/messages
 *   - google-gemini       → POST {baseUrl}/models/{modelId}:generateContent
 */
import { getPiSubProvider, type PiApiFlavor } from './sub-providers.js';

export interface PiTestCompletionInput {
  subProvider: string;
  apiKey?: string;
  baseUrl?: string;
  modelId?: string;
  prompt: string;
}

export interface PiTestCompletionResult {
  ok: boolean;
  text?: string;
  error?: string;
  status?: number;
  modelId?: string;
  subProvider?: string;
}

const REQUEST_TIMEOUT_MS = 30_000;

async function timedFetch(url: string, init: RequestInit): Promise<Response> {
  const ctl = new AbortController();
  const timer = setTimeout(() => ctl.abort(), REQUEST_TIMEOUT_MS);
  try {
    return await fetch(url, { ...init, signal: ctl.signal });
  } finally {
    clearTimeout(timer);
  }
}

function pickBaseUrl(input: PiTestCompletionInput): string | undefined {
  if (input.baseUrl?.trim()) return input.baseUrl.replace(/\/+$/, '');
  const def = getPiSubProvider(input.subProvider)?.baseUrl;
  return def?.replace(/\/+$/, '');
}

function pickModelId(input: PiTestCompletionInput): string | undefined {
  if (input.modelId?.trim()) return input.modelId.trim();
  return getPiSubProvider(input.subProvider)?.defaultModel;
}

export async function runPiTestCompletion(input: PiTestCompletionInput): Promise<PiTestCompletionResult> {
  const provider = getPiSubProvider(input.subProvider);
  if (!provider) {
    return { ok: false, error: `Unknown sub-provider: ${input.subProvider}` };
  }

  const baseUrl = pickBaseUrl(input);
  if (!baseUrl) return { ok: false, error: 'Missing base URL' };

  const modelId = pickModelId(input);
  if (!modelId) return { ok: false, error: 'Missing model ID' };

  if (provider.needsApiKey && !input.apiKey?.trim()) {
    return { ok: false, error: 'Missing API key' };
  }

  try {
    const text = await callByFlavor(provider.flavor, {
      baseUrl,
      modelId,
      apiKey: input.apiKey?.trim() || '',
      prompt: input.prompt,
    });
    return { ok: true, text, modelId, subProvider: provider.id };
  } catch (err: any) {
    return {
      ok: false,
      error: err?.message || String(err),
      status: err?.status,
      modelId,
      subProvider: provider.id,
    };
  }
}

interface DispatchArgs {
  baseUrl: string;
  modelId: string;
  apiKey: string;
  prompt: string;
}

async function callByFlavor(flavor: PiApiFlavor, args: DispatchArgs): Promise<string> {
  switch (flavor) {
    case 'openai-completions':
      return callOpenAICompletions(args);
    case 'anthropic-messages':
      return callAnthropicMessages(args);
    case 'google-gemini':
      return callGoogleGemini(args);
  }
}

/* ── OpenAI / OpenAI-compatible ── */

async function callOpenAICompletions({ baseUrl, modelId, apiKey, prompt }: DispatchArgs): Promise<string> {
  const headers: Record<string, string> = { 'content-type': 'application/json' };
  if (apiKey) headers['authorization'] = `Bearer ${apiKey}`;

  const res = await timedFetch(`${baseUrl}/chat/completions`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      model: modelId,
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 256,
      stream: false,
    }),
  });

  if (!res.ok) throw await httpError(res);

  const body: any = await res.json();
  const text = body?.choices?.[0]?.message?.content;
  if (typeof text !== 'string' || !text.trim()) {
    throw new Error(`Empty response (${JSON.stringify(body).slice(0, 200)})`);
  }
  return text.trim();
}

/* ── Anthropic Messages API ── */

async function callAnthropicMessages({ baseUrl, modelId, apiKey, prompt }: DispatchArgs): Promise<string> {
  const res = await timedFetch(`${baseUrl}/messages`, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01',
    },
    body: JSON.stringify({
      model: modelId,
      max_tokens: 256,
      messages: [{ role: 'user', content: prompt }],
    }),
  });

  if (!res.ok) throw await httpError(res);

  const body: any = await res.json();
  const block = Array.isArray(body?.content)
    ? body.content.find((b: any) => b?.type === 'text')
    : null;
  const text = block?.text;
  if (typeof text !== 'string' || !text.trim()) {
    throw new Error(`Empty response (${JSON.stringify(body).slice(0, 200)})`);
  }
  return text.trim();
}

/* ── Google Gemini ── */

async function callGoogleGemini({ baseUrl, modelId, apiKey, prompt }: DispatchArgs): Promise<string> {
  const url = `${baseUrl}/models/${encodeURIComponent(modelId)}:generateContent?key=${encodeURIComponent(apiKey)}`;
  const res = await timedFetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      contents: [{ role: 'user', parts: [{ text: prompt }] }],
      generationConfig: { maxOutputTokens: 256 },
    }),
  });

  if (!res.ok) throw await httpError(res);

  const body: any = await res.json();
  const parts: any[] = body?.candidates?.[0]?.content?.parts || [];
  const text = parts.map((p) => p?.text).filter(Boolean).join('\n').trim();
  if (!text) throw new Error(`Empty response (${JSON.stringify(body).slice(0, 200)})`);
  return text;
}

/* ── Helpers ── */

async function httpError(res: Response): Promise<Error> {
  let detail = '';
  try { detail = await res.text(); } catch {}
  const trimmed = detail.length > 400 ? `${detail.slice(0, 400)}…` : detail;
  const err: any = new Error(`HTTP ${res.status} ${res.statusText}${trimmed ? `: ${trimmed}` : ''}`);
  err.status = res.status;
  return err;
}
