/* eslint-disable @typescript-eslint/no-unused-vars */
import { config } from '../../lib/config.js';

// ============================================================================
// Azure OpenAI Embedding Client
// ============================================================================

const EMBEDDING_MODEL = 'text-embedding-3-small';
const EMBEDDING_DIMENSIONS = 1536;
const MAX_BATCH_SIZE = 100; // Azure OpenAI limit
const MAX_TOKENS_PER_REQUEST = 8192;

function logEmbeddingError(message: string, error: unknown): void {
  process.stderr.write(
    JSON.stringify({
      level: 'error',
      module: 'ai-diagnostics.embeddings',
      message,
      error: error instanceof Error ? error.message : String(error),
    }) + '\n'
  );
}

interface EmbeddingResponse {
  object: 'list';
  data: Array<{
    object: 'embedding';
    embedding: number[];
    index: number;
  }>;
  model: string;
  usage: {
    prompt_tokens: number;
    total_tokens: number;
  };
}

interface EmbeddingResult {
  embedding: number[];
  tokenCount: number;
  model: string;
}

interface BatchEmbeddingResult {
  embeddings: Array<{
    input: string;
    embedding: number[];
    index: number;
    tokenCount: number;
  }>;
  totalTokens: number;
  model: string;
}

/**
 * Generates embeddings for error text using Azure OpenAI
 */
export async function generateEmbedding(
  text: string,
  options: {
    model?: string;
    dimensions?: number;
  } = {}
): Promise<EmbeddingResult> {
  const model = options.model || EMBEDDING_MODEL;
  const apiKey = config.AZURE_OPENAI_KEY;
  const endpoint = config.AZURE_OPENAI_ENDPOINT;

  if (!apiKey || !endpoint) {
    throw new Error('Azure OpenAI credentials not configured');
  }

  const url = `${endpoint}/openai/deployments/${model}/embeddings?api-version=2024-02-01`;

  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'api-key': apiKey,
      },
      body: JSON.stringify({
        input: text,
        model,
        dimensions: options.dimensions || EMBEDDING_DIMENSIONS,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Azure OpenAI embedding failed: ${response.status} ${error}`);
    }

    const data = (await response.json()) as EmbeddingResponse;

    if (!data.data?.[0]?.embedding) {
      throw new Error('Invalid embedding response from Azure OpenAI');
    }

    return {
      embedding: data.data[0].embedding,
      tokenCount: data.usage?.prompt_tokens || 0,
      model: data.model,
    };
  } catch (error) {
    logEmbeddingError('Failed to generate embedding', error);
    throw error;
  }
}

/**
 * Generates embeddings for multiple texts in a batch
 * Respects Azure OpenAI batch limits
 */
export async function generateEmbeddingsBatch(
  texts: string[],
  options: {
    model?: string;
    dimensions?: number;
    batchSize?: number;
  } = {}
): Promise<BatchEmbeddingResult> {
  const batchSize = options.batchSize || MAX_BATCH_SIZE;
  const model = options.model || EMBEDDING_MODEL;

  if (texts.length === 0) {
    return { embeddings: [], totalTokens: 0, model };
  }

  // Process in chunks to respect batch limits
  const chunks: string[][] = [];
  for (let i = 0; i < texts.length; i += batchSize) {
    chunks.push(texts.slice(i, i + batchSize));
  }

  const allEmbeddings: BatchEmbeddingResult['embeddings'] = [];
  let totalTokens = 0;

  for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
    const chunk = chunks[chunkIndex];
    const apiKey = config.AZURE_OPENAI_KEY;
    const endpoint = config.AZURE_OPENAI_ENDPOINT;

    if (!apiKey || !endpoint) {
      throw new Error('Azure OpenAI credentials not configured');
    }

    const url = `${endpoint}/openai/deployments/${model}/embeddings?api-version=2024-02-01`;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'api-key': apiKey,
        },
        body: JSON.stringify({
          input: chunk,
          model,
          dimensions: options.dimensions || EMBEDDING_DIMENSIONS,
        }),
      });

      if (!response.ok) {
        const error = await response.text();
        throw new Error(`Azure OpenAI batch embedding failed: ${response.status} ${error}`);
      }

      const data = (await response.json()) as EmbeddingResponse;

      // Map results back to original inputs
      const chunkEmbeddings = data.data.map(item => ({
        input: chunk[item.index],
        embedding: item.embedding,
        index: chunkIndex * batchSize + item.index,
        tokenCount: Math.floor((data.usage?.prompt_tokens || 0) / chunk.length), // Approximate
      }));

      allEmbeddings.push(...chunkEmbeddings);
      totalTokens += data.usage?.prompt_tokens || 0;

      // Small delay between batches to avoid rate limits
      if (chunkIndex < chunks.length - 1) {
        await new Promise(resolve => setTimeout(resolve, 100));
      }
    } catch (error) {
      logEmbeddingError('Failed to generate batch embeddings', error);
      throw error;
    }
  }

  return {
    embeddings: allEmbeddings.sort((a, b) => a.index - b.index),
    totalTokens,
    model,
  };
}

// ============================================================================
// Error-Specific Embedding Functions
// ============================================================================

/**
 * Creates embedding text for an error cluster
 * Combines error type, message template, and stack signature
 */
export function createClusterEmbeddingText(
  errorType: string,
  messageTemplate: string,
  stackSignature: string
): string {
  const parts = [`Error: ${errorType}`, `Message: ${messageTemplate}`];

  if (stackSignature) {
    // Include top 3 stack frames for context
    const topFrames = stackSignature.split('|').slice(0, 3).join(' -> ');
    parts.push(`Stack: ${topFrames}`);
  }

  return parts.join('\n');
}

/**
 * Creates embedding text for semantic error search
 */
export function createSearchEmbeddingText(query: string): string {
  // Normalize query for better embedding matching
  return query
    .toLowerCase()
    .replace(/[^\w\s]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

// ============================================================================
// Vector Utilities
// ============================================================================

/**
 * Calculates cosine similarity between two vectors
 */
export function cosineSimilarity(a: number[], b: number[]): number {
  if (a.length !== b.length) {
    throw new Error('Vectors must have same dimensions');
  }

  let dotProduct = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  if (normA === 0 || normB === 0) {
    return 0;
  }

  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

/**
 * Calculates Euclidean distance between two vectors
 */
export function euclideanDistance(a: number[], b: number[]): number {
  if (a.length !== b.length) {
    throw new Error('Vectors must have same dimensions');
  }

  let sum = 0;
  for (let i = 0; i < a.length; i++) {
    const diff = a[i] - b[i];
    sum += diff * diff;
  }

  return Math.sqrt(sum);
}

/**
 * Normalizes a vector to unit length
 */
export function normalizeVector(vector: number[]): number[] {
  const norm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
  if (norm === 0) return vector;
  return vector.map(val => val / norm);
}

// ============================================================================
// Embedding Cache
// ============================================================================

interface CacheEntry {
  embedding: number[];
  timestamp: number;
}

class EmbeddingCache {
  private cache = new Map<string, CacheEntry>();
  private ttlMs: number;

  constructor(ttlHours: number = 24) {
    this.ttlMs = ttlHours * 60 * 60 * 1000;
  }

  get(text: string): number[] | null {
    const key = this.hashText(text);
    const entry = this.cache.get(key);

    if (!entry) return null;

    // Check TTL
    if (Date.now() - entry.timestamp > this.ttlMs) {
      this.cache.delete(key);
      return null;
    }

    return entry.embedding;
  }

  set(text: string, embedding: number[]): void {
    const key = this.hashText(text);
    this.cache.set(key, {
      embedding,
      timestamp: Date.now(),
    });
  }

  private hashText(text: string): string {
    // Simple hash for cache key
    let hash = 0;
    for (let i = 0; i < text.length; i++) {
      const char = text.charCodeAt(i);
      hash = (hash << 5) - hash + char;
      hash = hash & hash;
    }
    return hash.toString();
  }
}

// Global cache instance
export const embeddingCache = new EmbeddingCache(24);

/**
 * Generates embedding with caching
 */
export async function generateEmbeddingCached(
  text: string,
  options: {
    model?: string;
    dimensions?: number;
  } = {}
): Promise<EmbeddingResult> {
  // Check cache first
  const cached = embeddingCache.get(text);
  if (cached) {
    return {
      embedding: cached,
      tokenCount: 0,
      model: options.model || EMBEDDING_MODEL,
    };
  }

  // Generate new embedding
  const result = await generateEmbedding(text, options);

  // Cache it
  embeddingCache.set(text, result.embedding);

  return result;
}
