const database = require('../config/database');

/**
 * Feature Similarity Service
 * Handles duplicate detection and similarity scoring for custom features
 */
class FeatureSimilarityService {
  constructor() {
    this.database = database;
  }

  /**
   * Normalize text for comparison
   * @param {string} text - Text to normalize
   * @returns {string} - Normalized text
   */
  normalizeText(text) {
    if (!text) return '';
    return text.toLowerCase()
      .replace(/\s+/g, ' ')
      .trim()
      .replace(/[^\w\s]/g, ''); // Remove special characters
  }

  /**
   * Calculate similarity score between two strings using Levenshtein distance
   * @param {string} str1 - First string
   * @param {string} str2 - Second string
   * @returns {number} - Similarity score between 0 and 1
   */
  calculateSimilarity(str1, str2) {
    if (!str1 || !str2) return 0;
    
    const normalized1 = this.normalizeText(str1);
    const normalized2 = this.normalizeText(str2);
    
    if (normalized1 === normalized2) return 1.0;
    
    const longer = normalized1.length > normalized2.length ? normalized1 : normalized2;
    const shorter = normalized1.length > normalized2.length ? normalized2 : normalized1;
    
    if (longer.length === 0) return 1.0;
    
    const distance = this.levenshteinDistance(longer, shorter);
    return (longer.length - distance) / longer.length;
  }

  /**
   * Calculate Levenshtein distance between two strings
   * @param {string} str1 - First string
   * @param {string} str2 - Second string
   * @returns {number} - Levenshtein distance
   */
  levenshteinDistance(str1, str2) {
    const matrix = [];
    
    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }
    
    return matrix[str2.length][str1.length];
  }

  /**
   * Find features similar to the given name
   * @param {string} name - Feature name to search for
   * @param {number} threshold - Minimum similarity score (default: 0.7)
   * @param {number} limit - Maximum number of results (default: 5)
   * @returns {Promise<Array>} - Array of similar features
   */
  async findSimilarFeatures(name, threshold = 0.7, limit = 5) {
    try {
      const normalizedName = this.normalizeText(name);
      
      // 1. Check exact matches first
      const exactMatches = await this.database.query(`
        SELECT 
          tf.id, 
          tf.name, 
          'exact' as match_type, 
          1.0 as score,
          tf.feature_type,
          tf.complexity
        FROM template_features tf
        WHERE LOWER(tf.name) = $1
        UNION
        SELECT 
          tf.id, 
          tf.name, 
          'synonym' as match_type, 
          0.9 as score,
          tf.feature_type,
          tf.complexity
        FROM template_features tf
        JOIN feature_synonyms fs ON tf.id = fs.feature_id
        WHERE LOWER(fs.synonym) = $1
        LIMIT $2
      `, [normalizedName, limit]);
      
      if (exactMatches.rows.length > 0) {
        return exactMatches.rows;
      }
      
      // 2. Find fuzzy matches
      const allFeatures = await this.database.query(`
        SELECT 
          tf.id, 
          tf.name,
          tf.feature_type,
          tf.complexity
        FROM template_features tf
        UNION
        SELECT 
          tf.id, 
          tf.name,
          tf.feature_type,
          tf.complexity
        FROM template_features tf
        JOIN feature_synonyms fs ON tf.id = fs.feature_id
      `);
      
      const matches = [];
      for (const feature of allFeatures.rows) {
        const score = this.calculateSimilarity(name, feature.name);
        
        if (score >= threshold) {
          matches.push({
            ...feature,
            match_type: 'fuzzy',
            score: Math.round(score * 100) / 100 // Round to 2 decimal places
          });
        }
      }
      
      // Sort by score descending and limit results
      return matches
        .sort((a, b) => b.score - a.score)
        .slice(0, limit);
        
    } catch (error) {
      console.error('Error finding similar features:', error);
      throw new Error('Failed to find similar features');
    }
  }

  /**
   * Check if a feature name is a duplicate
   * @param {string} name - Feature name to check
   * @param {number} threshold - Similarity threshold (default: 0.8)
   * @returns {Promise<Object|null>} - Duplicate info or null
   */
  async checkForDuplicates(name, threshold = 0.8) {
    try {
      const similarFeatures = await this.findSimilarFeatures(name, threshold, 1);
      
      if (similarFeatures.length > 0) {
        const bestMatch = similarFeatures[0];
        return {
          isDuplicate: true,
          canonicalFeature: bestMatch,
          similarityScore: bestMatch.score,
          matchType: bestMatch.match_type
        };
      }
      
      return { isDuplicate: false };
    } catch (error) {
      console.error('Error checking for duplicates:', error);
      throw new Error('Failed to check for duplicates');
    }
  }

  /**
   * Add a synonym for a feature
   * @param {string} featureId - Feature ID
   * @param {string} synonym - Synonym to add
   * @param {string} createdBy - User who created the synonym
   * @returns {Promise<Object>} - Created synonym
   */
  async addSynonym(featureId, synonym, createdBy = 'admin') {
    try {
      const result = await this.database.query(`
        INSERT INTO feature_synonyms (feature_id, synonym, created_by)
        VALUES ($1, $2, $3)
        RETURNING *
      `, [featureId, synonym, createdBy]);
      
      return result.rows[0];
    } catch (error) {
      if (error.code === '23505') { // Unique constraint violation
        throw new Error('Synonym already exists');
      }
      console.error('Error adding synonym:', error);
      throw new Error('Failed to add synonym');
    }
  }

  /**
   * Get all synonyms for a feature
   * @param {string} featureId - Feature ID
   * @returns {Promise<Array>} - Array of synonyms
   */
  async getSynonyms(featureId) {
    try {
      const result = await this.database.query(`
        SELECT * FROM feature_synonyms
        WHERE feature_id = $1
        ORDER BY created_at DESC
      `, [featureId]);
      
      return result.rows;
    } catch (error) {
      console.error('Error getting synonyms:', error);
      throw new Error('Failed to get synonyms');
    }
  }
}

module.exports = FeatureSimilarityService;
