/**
 * POST /api/agents/[id]/test
 *
 * Reachability probe for an API profile. Resolves baseUrl + apiKey from
 * the saved profile (settings.agents[id]), then pings the provider's
 * /models endpoint:
 *
 *   - anthropic:    GET {baseUrl}/v1/models   x-api-key, anthropic-version
 *   - openai-shape: GET {baseUrl}/models      Authorization: Bearer
 *
 * Body (optional, all fields override saved values — lets the user test
 * before saving):
 *   { apiKey?, baseUrl?, provider?, model? }
 *
 * Result shape mirrors /api/connectors/[id]/test:
 *   { ok, status, message?, error?, duration_ms, body_preview? }
 */

import { NextResponse } from 'next/server';
import { loadSettings } from '@/lib/settings';
import { inferAdapter, pickApiKey, pickBaseUrl, defaultBaseUrl } from '@/lib/chat/agent-loop';

const DEFAULT_TIMEOUT_MS = 15_000;
const MAX_BODY_PREVIEW = 512;

interface TestResult {
  ok: boolean;
  status?: number;
  message?: string;
  error?: string;
  duration_ms?: number;
  body_preview?: string;
}

export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const settings = loadSettings();
  // Post-migration API profiles live in settings.apiProfiles; fall back to
  // settings.agents for the migration window or for legacy entries.
  const saved = (settings.apiProfiles || {})[id] || (settings.agents || {})[id] as any | undefined;

  let override: any = {};
  try { override = (await req.json()) ?? {}; } catch { override = {}; }

  // Settings GET masks secrets as '••••••••' before sending to the client;
   // if the user hasn't re-typed the API key, the form re-submits the mask.
   // Treat the sentinel as "use the saved value".
  const MASK = '••••••••';
  const unmask = (v: any, fallback: any) => (typeof v === 'string' && v === MASK ? fallback : v);

  // Merge — body overrides saved, but unsaved fields ok if body has them.
  const profile = {
    type: 'api' as const,
    provider: override.provider ?? saved?.provider,
    apiKey:   unmask(override.apiKey, saved?.apiKey) ?? saved?.apiKey,
    baseUrl:  override.baseUrl  ?? saved?.baseUrl,
    model:    override.model    ?? saved?.model,
    env:      (saved as any)?.env,   // legacy agent entries may have env; apiProfiles don't
  };

  if (!profile.provider) {
    return NextResponse.json<TestResult>({ ok: false, error: 'profile has no provider' });
  }
  const adapter = inferAdapter(profile.provider);
  const apiKey = pickApiKey(profile, adapter);
  if (!apiKey) {
    return NextResponse.json<TestResult>({ ok: false, error: 'apiKey is empty (and no fallback in env.*_API_KEY)' });
  }
  const baseUrl = pickBaseUrl(profile, adapter) || defaultBaseUrl(profile.provider);
  if (!baseUrl) {
    return NextResponse.json<TestResult>({ ok: false, error: `no baseUrl resolved for provider '${profile.provider}'` });
  }

  const url = adapter === 'anthropic'
    ? `${baseUrl.replace(/\/+$/, '').replace(/\/v1$/, '')}/v1/models`
    : `${baseUrl.replace(/\/+$/, '')}/models`;
  const headers: Record<string, string> = {};
  if (adapter === 'anthropic') {
    // Claude Code OAuth access tokens (sk-ant-oat*) use Bearer auth +
    // the oauth beta header; standard API keys (sk-ant-api*) use x-api-key.
    if (apiKey.startsWith('sk-ant-oat')) {
      headers['Authorization'] = `Bearer ${apiKey}`;
      headers['anthropic-beta'] = 'oauth-2025-04-20';
    } else {
      headers['x-api-key'] = apiKey;
    }
    headers['anthropic-version'] = '2023-06-01';
  } else {
    headers['Authorization'] = `Bearer ${apiKey}`;
  }

  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), DEFAULT_TIMEOUT_MS);
  const t0 = Date.now();
  let res: Response;
  try {
    res = await fetch(url, { method: 'GET', headers, signal: ctrl.signal });
  } catch (e) {
    clearTimeout(timer);
    const err = e as Error & { cause?: any };
    const causeMsg = err.cause?.message || err.cause?.code || '';
    return NextResponse.json<TestResult>({
      ok: false,
      error: `request failed: ${err.message}${causeMsg ? ` (${causeMsg})` : ''} → ${url}`,
      duration_ms: Date.now() - t0,
    });
  }
  clearTimeout(timer);
  const duration = Date.now() - t0;
  const text = await res.text().catch(() => '');
  const preview = text.length > MAX_BODY_PREVIEW ? text.slice(0, MAX_BODY_PREVIEW) + '…' : text;

  if (!res.ok) {
    let errMsg = `HTTP ${res.status} ${res.statusText}`;
    try {
      const j = JSON.parse(text);
      const detail = j?.error?.message || j?.error || j?.message;
      if (typeof detail === 'string') errMsg += `: ${detail}`;
    } catch {}
    return NextResponse.json<TestResult>({
      ok: false,
      status: res.status,
      error: errMsg,
      duration_ms: duration,
      body_preview: preview,
    });
  }

  // Parse model count + optionally verify cfg.model is in the list.
  let modelCount = 0;
  let modelFound: boolean | null = null;
  try {
    const j = JSON.parse(text);
    const arr = Array.isArray(j?.data) ? j.data : Array.isArray(j?.models) ? j.models : [];
    modelCount = arr.length;
    if (profile.model && arr.length > 0) {
      modelFound = arr.some((m: any) => (m?.id || m?.name) === profile.model);
    }
  } catch {}

  const parts = [`HTTP ${res.status}`, `${modelCount} model${modelCount === 1 ? '' : 's'}`];
  if (profile.model) {
    parts.push(modelFound === false ? `'${profile.model}' NOT in list` : `'${profile.model}' OK`);
  }
  return NextResponse.json<TestResult>({
    ok: true,
    status: res.status,
    message: parts.join(' · '),
    duration_ms: duration,
  });
}

