/**
 * Embedding + Qdrant client for vector search.
 *
 * Ollama nomic-embed-text (768 dim) → Qdrant collections.
 * Supports: products, datasheets, FAQs, manuals, troubleshooting, news.
 */

const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
const QDRANT_URL = process.env.QDRANT_URL || "http://localhost:6333";
const EMBED_MODEL = process.env.EMBED_MODEL || "nomic-embed-text";

export type CollectionName =
  | "product_embeddings"
  | "datasheet_chunks"
  | "faq_embeddings"
  | "manual_chunks"
  | "troubleshooting_embeddings"
  | "news_embeddings";

/** Generate embedding vector from text */
export async function embed(text: string): Promise<number[]> {
  const resp = await fetch(`${OLLAMA_URL}/api/embeddings`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ model: EMBED_MODEL, prompt: text }),
    signal: AbortSignal.timeout(30000),
  });

  if (!resp.ok) {
    throw new Error(`Ollama embed failed: ${resp.status} ${await resp.text()}`);
  }

  const data = await resp.json() as { embedding: number[] };
  return data.embedding;
}

/** Batch embed multiple texts */
export async function embedBatch(texts: ReadonlyArray<string>): Promise<number[][]> {
  const results: number[][] = [];
  // Ollama doesn't support batch embedding natively, so we serialize
  // with concurrency limit to avoid overloading
  const CONCURRENCY = 3;
  for (let i = 0; i < texts.length; i += CONCURRENCY) {
    const batch = texts.slice(i, i + CONCURRENCY);
    const embeddings = await Promise.all(batch.map((t) => embed(t)));
    results.push(...embeddings);
  }
  return results;
}

/** Upsert a point into Qdrant */
export async function upsertPoint(
  collection: CollectionName,
  id: string,
  vector: number[],
  payload: Record<string, unknown>,
): Promise<void> {
  const resp = await fetch(`${QDRANT_URL}/collections/${collection}/points`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      points: [{ id, vector, payload }],
    }),
    signal: AbortSignal.timeout(10000),
  });

  if (!resp.ok) {
    throw new Error(`Qdrant upsert failed: ${resp.status} ${await resp.text()}`);
  }
}

/** Batch upsert points */
export async function upsertPoints(
  collection: CollectionName,
  points: ReadonlyArray<{ id: string; vector: number[]; payload: Record<string, unknown> }>,
): Promise<void> {
  if (points.length === 0) return;

  const resp = await fetch(`${QDRANT_URL}/collections/${collection}/points`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ points }),
    signal: AbortSignal.timeout(30000),
  });

  if (!resp.ok) {
    throw new Error(`Qdrant batch upsert failed: ${resp.status} ${await resp.text()}`);
  }
}

/** Search Qdrant with vector similarity + optional payload filter */
export async function searchSimilar(
  collection: CollectionName,
  queryVector: number[],
  limit: number = 10,
  filter?: Record<string, unknown>,
): Promise<Array<{ id: string; score: number; payload: Record<string, unknown> }>> {
  const body: Record<string, unknown> = {
    vector: queryVector,
    limit,
    with_payload: true,
  };

  if (filter) {
    body.filter = filter;
  }

  const resp = await fetch(`${QDRANT_URL}/collections/${collection}/points/search`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(10000),
  });

  if (!resp.ok) {
    throw new Error(`Qdrant search failed: ${resp.status} ${await resp.text()}`);
  }

  const data = await resp.json() as { result: Array<{ id: string; score: number; payload: Record<string, unknown> }> };
  return data.result;
}

/** Semantic search: embed query text then search Qdrant */
export async function semanticSearch(
  collection: CollectionName,
  query: string,
  limit: number = 10,
  filter?: Record<string, unknown>,
): Promise<Array<{ id: string; score: number; payload: Record<string, unknown> }>> {
  const vector = await embed(query);
  return searchSimilar(collection, vector, limit, filter);
}

/** Get collection info (point count, etc.) */
export async function getCollectionInfo(
  collection: CollectionName,
): Promise<{ pointsCount: number; vectorsCount: number }> {
  const resp = await fetch(`${QDRANT_URL}/collections/${collection}`, {
    signal: AbortSignal.timeout(5000),
  });

  if (!resp.ok) {
    throw new Error(`Qdrant info failed: ${resp.status}`);
  }

  const data = await resp.json() as { result: { points_count: number; vectors_count: number } };
  return {
    pointsCount: data.result.points_count,
    vectorsCount: data.result.vectors_count,
  };
}
