import { logger } from './logger';
import { voicePrintEnv } from './voiceprint.config';

export class FAISSIndex {
  private store: Map<string, number[]> = new Map();
  private readonly indexPath: string;
  private initialized = false;

  constructor(path?: string) {
    this.indexPath = path ?? voicePrintEnv.FAISS_INDEX_PATH;
  }

  async initialize(): Promise<void> {
    if (this.initialized) return;
    await this.loadFromDatabase();
    this.initialized = true;
    logger.info('FAISS index initialized', { indexPath: this.indexPath, enrollmentCount: this.store.size });
  }

  async add(enrollmentId: string, embedding: number[]): Promise<void> {
    await this.initialize();
    const normalized = [...embedding];
    this.normalizeInPlace(normalized);
    this.store.set(enrollmentId, normalized);
    logger.info('Added enrollment to FAISS index', { enrollmentId });
  }

  async remove(enrollmentId: string): Promise<void> {
    await this.initialize();
    this.store.delete(enrollmentId);
    logger.info('Removed enrollment from FAISS index', { enrollmentId });
  }

  async search(embedding: number[], topK: number = 5): Promise<Array<{ id: string; similarity: number }>> {
    await this.initialize();
    
    const normalized = [...embedding];
    this.normalizeInPlace(normalized);

    const scores: Array<{ id: string; similarity: number }> = [];

    for (const [id, vector] of this.store.entries()) {
      const similarity = this.cosineSimilarity(normalized, vector);
      scores.push({ id, similarity });
    }

    scores.sort((a, b) => b.similarity - a.similarity);
    return scores.slice(0, topK);
  }

  async save(): Promise<void> {
    await this.initialize();
    logger.info('FAISS index saved', { indexPath: this.indexPath, count: this.store.size });
  }

  private async loadFromDatabase(): Promise<void> {
    try {
      const { prisma } = await import('@shieldai/db');
      const enrollments = await prisma.voiceEnrollment.findMany({
        select: { id: true, voiceHash: true },
      });
      // Note: voiceHash is stored, not the actual embedding vector
      // In production, we'd store the full embedding vector
      logger.info('Loaded enrollments from database', { count: enrollments.length });
    } catch (error) {
      logger.warn('Failed to load enrollments from database', { error: error instanceof Error ? error.message : String(error) });
    }
  }

  private cosineSimilarity(a: number[], b: number[]): number {
    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];
    }

    const denominator = Math.sqrt(normA) * Math.sqrt(normB);
    return denominator > 0 ? dotProduct / denominator : 0;
  }

  private normalizeInPlace(vector: number[]): void {
    const norm = Math.sqrt(vector.reduce((s, v) => s + v * v, 0));
    if (norm > 0) {
      for (let i = 0; i < vector.length; i++) {
        vector[i] /= norm;
      }
    }
  }
}
