import os
import configparser
import json

#aux functions
def empty_thread_id(thread_id):
    # Check if thread_id is None or empty string
    if not thread_id:
        return True
    
    # Check if thread_id contains only whitespace characters
    if thread_id.strip() == "":
        return True
    
    # Check if thread_id contains "empty" substring
    if "empty" in thread_id:
        return True
    
    # Check if thread_id contains "cuf" substring
    if "cuf" in thread_id:
        return True
    
    # In all other cases, return False
    return False


#Loads the .ini file from the /bobs folder for the matching bob_id
def load_bob_config(bob_id):

    if not bob_id:
        print("ERROR: Bob ID invalid or not found in request data.")
        return False

    
    config_path = f"bobs/{bob_id}.ini"

    if not os.path.exists(config_path):
        print("ERROR: Bob ID invalid, no " + bob_id + ".ini found in /bobs.")
        return False

    config = configparser.ConfigParser()
    config.read(config_path)
    print("INFO: Loaded Bob.")
    return config


#go through all the bobs ini files and check if there is one matching the login and pass
def check_bob_login(login, password):
    for ini_file in os.listdir('bobs/'):
        if ini_file.endswith('.ini'):
            config = configparser.ConfigParser()
            config.read(os.path.join('bobs', ini_file))
            if ((login == config['ID'].get('login')) and (password == config['ID'].get('access'))):
                return config['OpenAI'].get('bob_id')
    return None

    
#appends the new summary for a certain bob to its memories file
def append_memory(bob, summary):
    BOB_ID = bob['OpenAI']['bob_id']
    MEMORY = f"memories/" + BOB_ID + ".txt"

    with open(MEMORY, "a") as file:
        file.write(summary)
        file.write("\n\n")
        print("INFO: Summary appended to " + MEMORY)

#searches the products.json file for products that match the query and returns the top 5 results
def search_products(query):
    import re
    from difflib import SequenceMatcher

    def fuzzy_match(a, b, threshold=0.8):
        """Check if two strings are similar enough (fuzzy match)"""
        return SequenceMatcher(None, a, b).ratio() >= threshold

    def find_fuzzy_matches(word, text_words, threshold=0.75):
        """Find fuzzy matches for a word in a list of words"""
        matches = []
        for text_word in text_words:
            if len(word) >= 3 and len(text_word) >= 3:  # Only fuzzy match longer words
                if fuzzy_match(word, text_word, threshold):
                    matches.append(text_word)
        return matches

    # Load your product data
    with open('products.json', 'r', encoding='utf-8') as f:
        products = json.load(f)

    max_results = 5
    print("INFO: Searching for products matching query: " + query)

    # Split query into individual words and clean them
    query_words = [word.strip().lower() for word in re.split(r'[\s,]+', query) if word.strip()]

    # Score each product
    scored_products = []

    for product in products:
        nume_lower = product['nume'].lower()
        descriere_lower = product.get('descriere_ro', '').lower()

        # Combine both fields for searching
        combined_text = nume_lower + ' ' + descriere_lower

        # Split product text into words for fuzzy matching
        nume_words = re.findall(r'\b\w{3,}\b', nume_lower)  # Words with 3+ chars
        descriere_words = re.findall(r'\b\w{3,}\b', descriere_lower)
        all_words = nume_words + descriere_words

        score = 0
        matched_words = 0
        fuzzy_matches = 0

        # Score based on word matches
        for word in query_words:
            # Exact matches
            if word in nume_lower:
                score += 10  # Higher weight for name matches
                matched_words += 1
            elif word in descriere_lower:
                score += 5   # Lower weight for description matches
                matched_words += 1
            else:
                # Try fuzzy matching if no exact match
                fuzzy_name_matches = find_fuzzy_matches(word, nume_words, 0.8)
                fuzzy_desc_matches = find_fuzzy_matches(word, descriere_words, 0.75)

                if fuzzy_name_matches:
                    score += 7  # Fuzzy match in name (slightly lower than exact)
                    matched_words += 1
                    fuzzy_matches += 1
                elif fuzzy_desc_matches:
                    score += 3  # Fuzzy match in description
                    matched_words += 1
                    fuzzy_matches += 1

            # Bonus for partial matches in name (like "negru" matching "neagra")
            if any(word in part for part in nume_lower.split()):
                score += 2

        # Only include products that match at least one word
        if matched_words > 0:
            # Bonus for matching multiple words
            if len(query_words) > 1:
                score += matched_words * 2

            # Bonus for exact phrase match
            if query.lower() in combined_text:
                score += 15

            # Penalty for fuzzy matches to prefer exact matches
            score -= fuzzy_matches * 2

            # Only include if score is meaningful (filters out weak fuzzy matches)
            # Require strong matches - prevent irrelevant results that confuse the AI
            if score >= 15 or (fuzzy_matches == 0 and score >= 8):  # Much stricter threshold
                scored_products.append({
                    'product': product,
                    'score': score,
                    'matched_words': matched_words,
                    'fuzzy_matches': fuzzy_matches
                })

            # Early termination: if we have enough high-scoring exact matches, stop searching
            if len(scored_products) >= max_results * 3 and score >= 10:
                break

    # Sort by score (highest first)
    scored_products.sort(key=lambda x: x['score'], reverse=True)

    # Get top results - limit to exactly max_results (5)
    results = []
    for item in scored_products[:max_results]:
        results.append({
            'nume': item['product']['nume'],
            'url': item['product']['url']
        })

    print("INFO: Found " + str(len(results)) + " products matching query: " + query)
    if results:
        print("INFO: Top match score: " + str(scored_products[0]['score']))
        if scored_products[0]['fuzzy_matches'] > 0:
            print("INFO: Top result included fuzzy matches")

    return {
        'products': results,
        'total_found': min(len(scored_products), max_results),  # Limit total_found to max_results
        'search_query': query
    }

