#!/usr/bin/env node
/**
 * azure-embeddings.mjs — real-network verifier for the Loxia-backend
 * proxied Azure OpenAI Embeddings provider (i.e. `provider: "azure"`).
 *
 * WHY THIS LIVES OUTSIDE JEST
 *
 *   The unit tests in `azureProvider.test.js` use an injected `fetchImpl`
 *   that returns hand-crafted responses. They verify the request shape
 *   (URL = `${baseUrl}/llm/embed`, Authorization: Bearer <loxiaKey>,
 *   JSON body shape) and the response-parsing branches. They cannot
 *   tell you that:
 *     - The Loxia backend's /llm/embed route is actually deployed.
 *     - The backend's Azure deployment returns the dimensions our
 *       client expects.
 *     - Real HTTP errors (5xx, auth failures, rate limits) surface
 *       correctly through the wrapper.
 *
 *   So we run a real call against the configured backend here.
 *
 * REQUIRED ENV
 *
 *   LOXIA_AZURE_BASE_URL   the Loxia backend base URL (no trailing /llm/embed
 *                          — the provider appends it). e.g.
 *                          https://loxia-backend-autopilot.azurewebsites.net
 *   LOXIA_AZURE_API_KEY    the Loxia API key (the backend authenticates with
 *                          this; real Azure key never leaves the server).
 *
 * OPTIONAL ENV
 *
 *   LOXIA_AZURE_MODEL_ID   default text-embedding-3-small
 *   LOXIA_AZURE_DIMS       declared dimensions (default 1536)
 *
 * RUN
 *
 *   npm run verify:azure-embeddings
 *
 *   Exit codes:
 *     0  all checks passed
 *     1  one or more checks failed
 *     2  required env not set — surfaced explicitly, not silently
 *        passed; a CI step that skips this on missing secrets is an
 *        intentional decision, not an accident.
 */

import { AzureEmbeddingProvider } from '../../src/services/embeddings/providers/azureProvider.js';

const baseUrl  = process.env.LOXIA_AZURE_BASE_URL;
const apiKey   = process.env.LOXIA_AZURE_API_KEY;
const modelId  = process.env.LOXIA_AZURE_MODEL_ID || 'text-embedding-3-small';
const dims     = Number(process.env.LOXIA_AZURE_DIMS || 1536);

console.log('Azure (Loxia-backend-proxied) embeddings real-network verifier');
if (!baseUrl || !apiKey) {
  console.error('');
  console.error('SKIP: required env vars not set.');
  console.error('  LOXIA_AZURE_BASE_URL:  ' + (baseUrl ? 'set' : 'MISSING'));
  console.error('  LOXIA_AZURE_API_KEY:   ' + (apiKey  ? 'set' : 'MISSING'));
  console.error('');
  console.error('CI must either configure these secrets and re-run, or');
  console.error('explicitly skip this step (and accept the coverage gap).');
  process.exit(2);
}

console.log(`Backend:    ${baseUrl}`);
console.log(`Model:      ${modelId}`);
console.log(`Dimensions: ${dims}`);
console.log('');

const results = [];
let failures = 0;

function check(name, fn) {
  return (async () => {
    const t0 = Date.now();
    try {
      await fn();
      const ms = Date.now() - t0;
      results.push({ name, ok: true, ms });
      console.log(`  ✔ ${name} (${ms}ms)`);
    } catch (err) {
      failures++;
      const ms = Date.now() - t0;
      results.push({ name, ok: false, ms, err: err?.message });
      console.error(`  ✗ ${name} (${ms}ms)`);
      console.error(`      ${err?.stack || err}`);
    }
  })();
}

function dot(a, b) {
  let s = 0;
  for (let i = 0; i < a.length; i++) s += a[i] * b[i];
  return s;
}

const provider = new AzureEmbeddingProvider({
  baseUrl,
  getApiKey: () => apiKey,
  modelId,
  dimensions: dims,
});

await check('single embedQuery returns Float32Array of declared dim', async () => {
  const vec = await provider.embedQuery('hello world');
  if (!(vec instanceof Float32Array)) throw new Error('not a Float32Array');
  if (vec.length !== dims) {
    throw new Error(`backend returned ${vec.length}d but client declared ${dims}d`);
  }
  let sum = 0;
  for (let i = 0; i < vec.length; i++) sum += vec[i] * vec[i];
  const mag = Math.sqrt(sum);
  if (Math.abs(mag - 1) > 1e-3) throw new Error(`magnitude=${mag.toFixed(6)} (expected ≈1)`);
});

await check('batch embedDocuments returns one vector per input', async () => {
  const inputs = ['retry logic', 'rate limiting', 'circuit breakers'];
  const vecs = await provider.embedDocuments(inputs);
  if (!Array.isArray(vecs)) throw new Error('not an array');
  if (vecs.length !== inputs.length) throw new Error(`got ${vecs.length} vectors for ${inputs.length} inputs`);
  for (let i = 0; i < vecs.length; i++) {
    if (!(vecs[i] instanceof Float32Array)) throw new Error(`vec[${i}] not Float32Array`);
    if (vecs[i].length !== dims) throw new Error(`vec[${i}].length=${vecs[i].length}, expected ${dims}`);
  }
});

await check('cosine similarity is semantically meaningful', async () => {
  const seed       = await provider.embedQuery('how do I throttle rate-limited API requests?');
  const paraphrase = await provider.embedQuery('handling rate limits in HTTP API clients');
  const unrelated  = await provider.embedQuery('recipe for chocolate chip cookies');
  const sp = dot(seed, paraphrase);
  const su = dot(seed, unrelated);
  if (sp <= su) throw new Error(`paraphrase(${sp.toFixed(3)}) ≤ unrelated(${su.toFixed(3)})`);
  if ((sp - su) <= 0.03) throw new Error(`gap=${(sp - su).toFixed(3)} too small (expected >0.03)`);
});

await check('provider.health reports ok', async () => {
  const h = await provider.health();
  if (!h.ok) throw new Error(`health.ok=${h.ok} (${h.reason || 'no reason'})`);
});

console.log('');
const passed = results.filter(r => r.ok).length;
const total = results.length;
console.log(`${failures === 0 ? 'PASS' : 'FAIL'}: ${passed}/${total} checks passed`);
process.exit(failures === 0 ? 0 : 1);
