/**
 * Fuzzy Search Service
 *
 * Provides fuzzy search capabilities for bookings using PostgreSQL full-text search
 * and Levenshtein distance for typo tolerance
 */

import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BookingOrmEntity } from '../../infrastructure/persistence/typeorm/entities/booking.orm-entity';

@Injectable()
export class FuzzySearchService {
  private readonly logger = new Logger(FuzzySearchService.name);

  constructor(
    @InjectRepository(BookingOrmEntity)
    private readonly bookingOrmRepository: Repository<BookingOrmEntity>
  ) {}

  /**
   * Fuzzy search for bookings by booking number, shipper, or consignee
   * Uses PostgreSQL full-text search with trigram similarity
   */
  async fuzzySearchBookings(
    searchTerm: string,
    organizationId: string,
    limit: number = 20
  ): Promise<BookingOrmEntity[]> {
    if (!searchTerm || searchTerm.length < 2) {
      return [];
    }

    this.logger.log(`Fuzzy search for "${searchTerm}" in organization ${organizationId}`);

    // Use PostgreSQL full-text search with similarity
    // This requires pg_trgm extension to be enabled
    const results = await this.bookingOrmRepository
      .createQueryBuilder('booking')
      .leftJoinAndSelect('booking.containers', 'containers')
      .where('booking.organization_id = :organizationId', { organizationId })
      .andWhere(
        `(
          similarity(booking.booking_number, :searchTerm) > 0.3
          OR booking.booking_number ILIKE :likeTerm
          OR similarity(booking.shipper_name, :searchTerm) > 0.3
          OR booking.shipper_name ILIKE :likeTerm
          OR similarity(booking.consignee_name, :searchTerm) > 0.3
          OR booking.consignee_name ILIKE :likeTerm
        )`,
        {
          searchTerm,
          likeTerm: `%${searchTerm}%`,
        }
      )
      .orderBy(
        `GREATEST(
          similarity(booking.booking_number, :searchTerm),
          similarity(booking.shipper_name, :searchTerm),
          similarity(booking.consignee_name, :searchTerm)
        )`,
        'DESC'
      )
      .setParameter('searchTerm', searchTerm)
      .limit(limit)
      .getMany();

    this.logger.log(`Found ${results.length} results for fuzzy search`);

    return results;
  }

  /**
   * Search for bookings using PostgreSQL full-text search with ts_vector
   * This provides better performance for large datasets
   */
  async fullTextSearch(
    searchTerm: string,
    organizationId: string,
    limit: number = 20
  ): Promise<BookingOrmEntity[]> {
    if (!searchTerm || searchTerm.length < 2) {
      return [];
    }

    this.logger.log(`Full-text search for "${searchTerm}" in organization ${organizationId}`);

    // Convert search term to tsquery format
    const tsquery = searchTerm
      .split(/\s+/)
      .filter(term => term.length > 0)
      .map(term => `${term}:*`)
      .join(' & ');

    const results = await this.bookingOrmRepository
      .createQueryBuilder('booking')
      .leftJoinAndSelect('booking.containers', 'containers')
      .where('booking.organization_id = :organizationId', { organizationId })
      .andWhere(
        `(
          to_tsvector('english', booking.booking_number) @@ to_tsquery('english', :tsquery)
          OR to_tsvector('english', booking.shipper_name) @@ to_tsquery('english', :tsquery)
          OR to_tsvector('english', booking.consignee_name) @@ to_tsquery('english', :tsquery)
          OR booking.booking_number ILIKE :likeTerm
        )`,
        {
          tsquery,
          likeTerm: `%${searchTerm}%`,
        }
      )
      .orderBy('booking.created_at', 'DESC')
      .limit(limit)
      .getMany();

    this.logger.log(`Found ${results.length} results for full-text search`);

    return results;
  }

  /**
   * Combined search that tries fuzzy search first, falls back to full-text if no results
   */
  async search(
    searchTerm: string,
    organizationId: string,
    limit: number = 20
  ): Promise<BookingOrmEntity[]> {
    // Try fuzzy search first (more tolerant to typos)
    let results = await this.fuzzySearchBookings(searchTerm, organizationId, limit);

    // If no results, try full-text search
    if (results.length === 0) {
      results = await this.fullTextSearch(searchTerm, organizationId, limit);
    }

    return results;
  }
}
