import requests
import re
import json
from app import mysql
from app.config import Config
from app.routes.api import create_meeting_prompt
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse, Gather
import logging
from datetime import datetime
import uuid
from app.utils.celery_app import celery
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
import threading
from contextlib import contextmanager
import mysql.connector.pooling as pooling
import time
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache
import hashlib
from app.utils.db_manager import db_manager
import random

logger = logging.getLogger(__name__)

# Initialize the embeddings model
_embeddings = None
_embeddings_lock = threading.Lock()

# Initialize the response cache
_response_cache = {}
_response_cache_lock = threading.Lock()

_vector_store_cache = {}
_vector_store_cache_lock = threading.Lock()

def get_embeddings():
    global _embeddings
    if _embeddings is None:
        with _embeddings_lock:
            if _embeddings is None:
                _embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
    return _embeddings

# Add these constants
MAX_RETRIES = 3
RETRY_DELAY = 1

@contextmanager
def get_db_connection():
    """
    Get a database connection from the pool with automatic cleanup
    """
    connection = None
    cursor = None
    try:
        connection = db_manager.get_connection()
        cursor = connection.cursor()
        yield cursor
    except Exception as e:
        logger.error(f"Database connection error: {str(e)}")
        raise
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()  # This returns the connection to the pool

def execute_query(query, params=None, fetch=True):
    """
    Execute a database query with retry logic and connection management
    """
    max_retries = 3
    retry_delay = 1
    
    for attempt in range(max_retries):
        try:
            with get_db_connection() as cursor:
                cursor.execute(query, params or ())
                if fetch:
                    return cursor.fetchall()
                return None
        except mysql.connector.errors.DatabaseError as e:
            if "Too many connections" in str(e):
                reset_connections()
                if attempt == max_retries - 1:
                    raise
                time.sleep(retry_delay)
            else:
                raise
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(retry_delay)

def create_or_get_vector_store(company_id, query_executor):
    """
    Create or retrieve the vector store for a company's documents.
    Accepts a query_executor function (like execute_query) to interact with the database.
    """
    logger.debug(f"Attempting to get/create vector store for company_id: {company_id}")
    try:
        # Check cache first
        if company_id in _vector_store_cache:
            return _vector_store_cache[company_id]
        with _vector_store_cache_lock:
            if company_id in _vector_store_cache:
                return _vector_store_cache[company_id]
        
        # Check if vector store exists in database
        result = query_executor(f"""
            SELECT vector_store 
            FROM company_vector_stores 
            WHERE company_id = %s
        """, (company_id,))
        
        if result and result[0] and result[0][0]:
            logger.debug(f"Found existing vector store for company_id: {company_id}. Deserializing...")
            vector_store = FAISS.deserialize_from_bytes(
                embeddings=get_embeddings(),
                serialized=result[0][0],
                allow_dangerous_deserialization=True
            )
            logger.debug(f"Vector store deserialized successfully for company_id: {company_id}.")
            _vector_store_cache[company_id] = vector_store
            return vector_store
            
        logger.debug(f"No existing vector store found or it was empty for company_id: {company_id}. Proceeding to create a new one.")
        
        # Fetch documents to build the new vector store
        documents = query_executor("""
            SELECT id, file_content, created_at
            FROM company_documents 
            WHERE company_id = %s
        """, (company_id,))
        
        if not documents:
            logger.warning(f"No documents found in 'company_documents' table for company_id: {company_id}. Cannot create vector store.")
            return None
            
        texts = []
        metadatas = []
        
        # Prepare documents for vector store by chunking
        for doc_id, content, created_at in documents:
            text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=1000,
                chunk_overlap=200,
                length_function=len,
            )
            chunks = text_splitter.split_text(content)
            
            for chunk in chunks:
                texts.append(chunk)
                # Ensure created_at is in a serializable format for metadata
                metadatas.append({
                    "doc_id": doc_id,
                    "created_at": created_at.isoformat() if hasattr(created_at, 'isoformat') else str(created_at)
                })
        
        if not texts:
            logger.warning(f"No text chunks generated from documents for company_id: {company_id}. Vector store will not be created.")
            return None

        # Create the FAISS vector store from texts and embeddings
        logger.debug(f"Creating FAISS vector store from {len(texts)} chunks for company_id: {company_id}.")
        vector_store = FAISS.from_texts(
            texts=texts,
            embedding=get_embeddings(),
            metadatas=metadatas
        )
        logger.debug(f"Vector store created in memory for company_id: {company_id}. Attempting to serialize and save to DB.")
        
        # Serialize the vector store to bytes for storage
        serialized = vector_store.serialize_to_bytes()
        
        # Save or update the vector store in the database
        query_executor("""
            INSERT INTO company_vector_stores (company_id, vector_store)
            VALUES (%s, %s)
            ON DUPLICATE KEY UPDATE vector_store = %s
        """, (company_id, serialized, serialized), fetch=False) # Use fetch=False for non-SELECT queries
        
        logger.info(f"Vector store successfully saved/updated in database for company_id: {company_id}.")
        
        _vector_store_cache[company_id] = vector_store
        return vector_store
        
    except Exception as e:
        # Log the full traceback for detailed debugging
        logger.error(f"Critical error in create_or_get_vector_store for company_id {company_id}: {str(e)}", exc_info=True)
        return None

def generate_llm_reply(company_id, contact_email, contact_name, user_message, campaign_id=None, contact_id=None):
    cursor = mysql.connection.cursor()
        
    # Get company basic information
    cursor.execute("""
        SELECT name, instructions, website, welcome_message
        FROM companies 
        WHERE id = %s
    """, (company_id,))
    company = cursor.fetchone()
    if not company:
        return "Thank you for your reply. We will get back to you soon."
    # Get most relevant documents based on user query
    cursor.execute("""
        SELECT file_content 
        FROM company_documents 
        WHERE company_id = %s
        ORDER BY created_at DESC
        LIMIT 10
    """, (company_id,))
    documents = cursor.fetchall()
    
    # Combine document content
    doc_content = "\n---\n".join([doc[0] for doc in documents]) if documents else ""

    # 2. Get company settings
    cursor.execute("""
        SELECT available_days, start_time, end_time 
        FROM company_settings 
        WHERE company_id = %s
    """, (company_id,))
    settings = cursor.fetchone()
    settings_info = ""
    if settings:
        available_days = settings[0].split(',') if settings[0] else []
        start_time = settings[1]
        end_time = settings[2]
        settings_info = f"""
        Meeting Availability:
        - Available Days: {', '.join(available_days)}
        - Time Slot: {start_time} to {end_time}
        """
    else:
        settings_info = "No meeting settings configured yet."

    # 3. Get website URLs
    cursor.execute("""
        SELECT url, page_title 
        FROM website_pages 
        WHERE company_id = %s
    """, (company_id,))
    available_urls = cursor.fetchall()
    url_context = "Available pages:\n"
    if available_urls:
        for url, title in available_urls:
            url_context += f"- {title}: {url}\n"
    else:
        url_context = f"Main website: {company[2]}\n"

    company_info = ""
    if company[1]:  # instructions
        company_info += f"General Information:\n{company[1]}\n\n"
    
    if doc_content:
        company_info += f"Additional Information from Documents:\n{doc_content}\n\n"

    # 4. Compose system message (same as company_chat)
    system_message = {
        "role": "system",
        "content": f"""You are a customer service representative for {company[0]}. 
Keep your responses brief and professional.

Company Information:
{company_info}

Website Information:
{url_context}

{settings_info}

Meeting Scheduling Instructions:
{create_meeting_prompt(company[0])}

Guidelines:
1. Use information from both the provided instructions and PDF documents to give accurate responses
2. Keep responses concise and relevant
3. If information is available in multiple sources, combine it appropriately
4. For website-related queries, only use the URLs provided above
5. If you don't have enough information to answer accurately, say so
6. For meeting requests, strictly follow the meeting scheduling process
7. If customer starts conversation with hello or similar greetings, respond briefly and ask how you can help
8. Track meeting scheduling state and only proceed when all required information is collected
9. When scheduling meetings, ensure the requested day and time are within the company's available slots
10. If a customer requests a meeting outside available hours, inform them of the available slots
11. You are both a text and voice-based agent - adapt your responses appropriately based on whether the user is typing or speaking
12. When replying to a customer email, address them by their name if possible
13. Your reply will be sent as an email, so be polite and clear.
12. IMPORTANT: Only include the [ACTION]{{...}}[/ACTION] tag for scheduling a meeting when the user explicitly asks to schedule a meeting AND provides all necessary information (email, desired date, and desired time). Do not include the [ACTION] tag for any other type of query.

"""
    }

    # 5. Fetch previous conversation if campaign_id and contact_id are provided
    messages = [system_message]
    if campaign_id and contact_id:
        cursor.execute("""
            SELECT reply_content, is_ai_response
            FROM email_replies
            WHERE campaign_id = %s AND contact_id = %s
            ORDER BY created_at ASC
        """, (campaign_id, contact_id))
        history = cursor.fetchall()
        for content, is_ai in history:
            if is_ai:
                messages.append({"role": "assistant", "content": content})
            else:
                messages.append({"role": "user", "content": content})

    # 6. Add the latest user message
    messages.append({"role": "user", "content": user_message})

    # 7. Call Groq API
    response = requests.post(
        Config.GROQ_API_URL,
        headers={
            "Authorization": f"Bearer {Config.GROQ_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "model": "meta-llama/llama-4-scout-17b-16e-instruct",
            "messages": messages,
            "temperature": 1,
            "max_tokens": 1000,
            "top_p": 1,
            "stream": False
        }
    )
    if response.status_code == 200:
        ai_response = response.json()["choices"][0]["message"]["content"]

        # Optionally, handle [ACTION] blocks for meeting scheduling, as in your chat widget
        action_pattern = re.compile(r'\[ACTION\](.*?)\[/ACTION\]', re.DOTALL)
        action_match = action_pattern.search(ai_response)
        if action_match:
            try:
                action_data = json.loads(action_match.group(1))
                if action_data.get('action') == 'schedule_meeting':
                    # Return a dictionary instead of a string
                    return {
                        "response": "Thank you for your interest in scheduling a meeting. Our team will contact you soon to confirm the details.",
                        "action": action_data  # Include the action data
                    }
            except Exception as e:
                return {
                    "response": f"Error processing your request: {str(e)}",
                    "action": None
                }
        
        # For normal responses, return a dictionary
        clean_text = strip_ssml(ai_response)
        return {
            "response": clean_text,
            "action": None
        }
    else:
        return "Thank you for your reply. We will get back to you soon."

@lru_cache(maxsize=1000)
def cache_key(company_id, user_input, script, call_status):
    """Generate a cache key for the response"""
    key = f"{company_id}:{user_input}:{script}:{call_status}"
    return hashlib.md5(key.encode()).hexdigest()

def generate_call_response(company_id, user_input, script, call_status=None, call_sid=None, contact_id=None):
    """Generate AI response for call campaigns - optimized for natural conversation"""
    try:
        import time
        t0 = time.time()
        # Handle None input
        if user_input is None:
            user_input = ""

        # Process script segments
        script_segments = [s.strip() for s in script.split('\n\n') if s.strip()]
        current_segment = get_current_segment(call_status, script_segments)
        current_script = script_segments[current_segment] if current_segment < len(script_segments) else ""

        # Get company data
        company_data = get_company_data(company_id)
        if not company_data:
            return {
                "response": "Thank you for your time. Goodbye.",
                "fulfilled": True  # End of conversation
            }
        logger.info(f"Company data: {time.time() - t0:.2f}s")

        logger.info(f"user_input: {repr(user_input)}, current_segment: {current_segment}")
        if not user_input and current_segment == 0:
            # Start background thread to load vector store
            logger.info(f"inside if condition of latency")

            def preload_vector():
                create_or_get_vector_store(company_id, execute_query)
            threading.Thread(target=preload_vector, daemon=True).start()
            return {
                "response": current_script,
                "fulfilled": False
            }

        # For all other segments, proceed as before (vector store will be cached if already loaded)
        vector_store = create_or_get_vector_store(company_id, execute_query)
        doc_content = get_document_content(vector_store, user_input)
        logger.info(f"Vector store: {time.time() - t0:.2f}s")

        # Prepare messages for the API call
        messages = []
        
        # Add system message with emphasis on natural conversation
        messages.append({
            "role": "system",
            "content": f"""
            You are a professional sales representative for {company_data[0]}. 
You are making an outbound call to a customer.

IMPORTANT GUIDELINES:
1. Sound like a real person having a natural conversation
    -Vary your sentence structure and avoid sounding robotic.
    -If you need a moment to think, use a filler or a pause.
2. Use natural conversational fillers and transitions (e.g., "hmm", "ah", "let me think", "okay", "sure", "great question", "well", "so", "I see", "right", "alright", "let's see", "just a moment", "thanks for sharing that", etc.).
3. Occasionally add a short pause (e.g., "..." or "—") to simulate thinking or emotion.
4. If the customer asks a question:
   - Answer it directly and naturally
   - Use the company information to provide accurate details
   - After answering, smoothly transition back to the script and do not repeat the same script segment
5. If the customer seems confused or asks for clarification:
   - Explain things clearly and conversationally
   - Use examples or analogies if helpful
   - Make sure they understand before moving on
6. If the customer shows interest:
   - Build on their interest naturally
   - Share relevant information from the script and the company information if needed
   - Guide them toward the next step
7. If the customer seems hesitant:
   - Address their concerns directly
   - Share relevant benefits or information
   - Be patient and understanding
8. Always maintain a friendly, professional tone
9. Never mention that you're an AI or automated system
10. Use natural conversational fillers and transitions
11. Use stored transcript of running call and ensure that you are not repeating the same script segment.
12. If the customer wants to end the call:
   - Thank them politely
   - Ask if they have any final questions
   - Say goodbye professionally


Remember:
- This is a real phone conversation - keep it natural and engaging
- Listen to the customer's concerns and address them directly
- Use the script as a guide, but adapt to the conversation flow
- Make the customer feel comfortable and valued
- If they ask about the purpose of the call, explain it naturally using the script
- If they ask why you're calling, explain the value proposition from the script
- Show acknowledgement of the user's response each time.
- Do not repeat previous script segments or introductions.
- Introduce yourself only once and then do not repeat it again until customer asks you to introduce yourself again.
- If the customer asks something outside the script and you answer using company information, first provide a clear and satisfying answer, confirm with the customer that their question is addressed, and then smoothly return to the appropriate script segment, resuming from where you left off or where the customer diverged from the script.
- If the customer does not respond to your greeting, do not repeat the greeting. Instead, say something like 'Hello? Are you there?' or 'Can you hear me?' up to 2 times, then politely end the call.
- Always acknowledge the user's input with a brief, natural phrase before answering (e.g., "That's a great question!", "I see what you mean.", "Thanks for sharing that.", "Absolutely!", "Good point.").

SCRIPT SEGMENTS:
{chr(10).join([f"{i+1}. {seg}" for i, seg in enumerate(script_segments)])}

CURRENT SEGMENT TO SPEAK:
{current_script}

PREVIOUSLY SPOKEN SEGMENTS:
{chr(10).join(script_segments[:current_segment]) if current_segment > 0 else "None"}

COMPANY INFO (use ONLY if the script does not answer the question):
{doc_content}
"""
        })
        
        # Add conversation history for context
        if call_sid and contact_id:
            history = get_conversation_history(contact_id, call_sid, limit=None)
            for user_msg, ai_msg, _ in history:
                if user_msg:
                    messages.append({"role": "user", "content": user_msg})
                if ai_msg:
                    messages.append({"role": "assistant", "content": ai_msg})
        
        # Add current user input
        messages.append({"role": "user", "content": user_input})

        try:
            # Make LLM API call with appropriate parameters
            t1 = time.time()
            response = requests.post(
                Config.GROQ_API_URL,
                headers={
                    "Authorization": f"Bearer {Config.GROQ_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": "meta-llama/llama-4-scout-17b-16e-instruct",
                    "messages": messages,
                    "temperature": 0.8,
                    "max_tokens": 100,
                    "top_p": 1,
                    "stream": False
                },
                timeout=1.5  # Increased timeout for better responses
            )
            logger.info(f"LLM API call: {time.time() - t1:.2f}s")
            logger.info(f"Total generate_call_response: {time.time() - t0:.2f}s")

            if response.status_code == 200:
                ai_response = response.json()["choices"][0]["message"]["content"]
                ai_response = add_human_pauses(ai_response)
                # Check if the segment's condition was fulfilled by the user's reply
                fulfilled = is_segment_fulfilled(current_script, user_input)
                
                clean_text = strip_ssml(ai_response)
                return {"response": clean_text, "fulfilled": fulfilled}
                
        except requests.exceptions.Timeout:
            logger.warning("Except part of LLM part")

            try:
                fallback_messages = []
                # Simpler system prompt, but still include script and company info
                fallback_messages.append({
                    "role": "system",
                    "content": f"""You are a professional sales representative for {company_data[0]}.
You are making an outbound call to a customer. Please answer naturally and professionally.
Current script segment: {current_script}
Company info: {doc_content}
"""
                })
                # Add previous conversation history if available
                if call_sid and contact_id:
                    history = get_conversation_history(contact_id, call_sid, limit=None)
                    for user_msg, ai_msg, _ in history:
                        if user_msg:
                            fallback_messages.append({"role": "user", "content": user_msg})
                        if ai_msg:
                            fallback_messages.append({"role": "assistant", "content": ai_msg})
                # Add current user input
                fallback_messages.append({"role": "user", "content": user_input})

                response = requests.post(
                    Config.GROQ_API_URL,
                    headers={
                        "Authorization": f"Bearer {Config.GROQ_API_KEY}",
                        "Content-Type": "application/json"
                    },
                    json={
                        "model": "meta-llama/llama-4-scout-17b-16e-instruct",
                        "messages": fallback_messages,
                        "temperature": 0.8,
                        "max_tokens": 100,
                        "top_p": 1,
                        "stream": False
                    },
                    timeout=1.0
                )

                if response.status_code == 200:
                    ai_response = response.json()["choices"][0]["message"]["content"]
                    fulfilled = is_segment_fulfilled(current_script, user_input)
                    ai_response = add_human_pauses(ai_response)
                    clean_text = strip_ssml(ai_response)
                    return {"response": clean_text, "fulfilled": fulfilled}
            except:
                pass
                
            # If both attempts fail, return a natural response and don't advance
            logger.warning("LLM API calls timed out, returning natural response")
            fallback_response = f"I'm calling from {company_data[0]}. I'd like to tell you about our services. Would that be okay?"
            # Add human pauses
            fallback_response = add_human_pauses(fallback_response)
            clean_text = strip_ssml(fallback_response)
            return {"response": clean_text, "fulfilled": False}
            
        except Exception as e:
            logger.error(f"Error in LLM API call: {str(e)}")
            fallback_response = f"I'm calling from {company_data[0]}. I'd like to tell you about our services. Would that be okay?"
            # Add human pauses
            fallback_response = add_human_pauses(fallback_response)
            clean_text = strip_ssml(fallback_response)
            return {"response": clean_text, "fulfilled": False}

    except Exception as e:
        logger.error(f"Error in generate_call_response: {str(e)}")
        # Use a generic response if company_data is not available
        company_name = company_data[0] if 'company_data' in locals() and company_data else "the company"
        fallback_response = f"I'm calling from {company_name}. I'd like to tell you about our services. Would that be okay?"
        # Add human pauses
        fallback_response = add_human_pauses(fallback_response)
        clean_text = strip_ssml(fallback_response)
        return {"response": clean_text, "fulfilled": False}

def extract_service_info(doc_content):
    """
    Extract service information from company documents
    """
    try:
        # Use the AI to extract relevant service information
        messages = [
            {
                "role": "system",
                "content": "Extract the most relevant service information from the following company documents. Focus on key features and benefits. Keep it concise and natural for a phone conversation."
            },
            {
                "role": "user",
                "content": doc_content
            }
        ]
        
        response = requests.post(
            Config.GROQ_API_URL,
            headers={
                "Authorization": f"Bearer {Config.GROQ_API_KEY}",
                "Content-Type": "application/json"
            },
            json={
                "model": "meta-llama/llama-4-scout-17b-16e-instruct",
                "messages": messages,
                "temperature": 0.7,
                "max_tokens": 200,
                "top_p": 1,
                "stream": False
            }
        )
        
        if response.status_code == 200:
            return response.json()["choices"][0]["message"]["content"]
        return "our comprehensive service package"
    except Exception as e:
        logger.error(f"Error extracting service info: {str(e)}")
        return "our comprehensive service package"

# def extract_feature_info(doc_content):
#     """
#     Extract feature information from company documents
#     """
#     try:
#         # Use the AI to extract relevant feature information
#         messages = [
#             {
#                 "role": "system",
#                 "content": "Extract the most relevant feature information from the following company documents. Focus on key features that would be most beneficial to customers. Keep it concise and natural for a phone conversation."
#             },
#             {
#                 "role": "user",
#                 "content": doc_content
#             }
#         ]
        
#         response = requests.post(
#             Config.GROQ_API_URL,
#             headers={
#                 "Authorization": f"Bearer {Config.GROQ_API_KEY}",
#                 "Content-Type": "application/json"
#             },
#             json={
#                 "model": "meta-llama/llama-4-scout-17b-16e-instruct",
#                 "messages": messages,
#                 "temperature": 0.7,
#                 "max_tokens": 200,
#                 "top_p": 1,
#                 "stream": False
#             }
#         )
        
#         if response.status_code == 200:
#             return response.json()["choices"][0]["message"]["content"]
#         return "our premium features"
#     except Exception as e:
#         logger.error(f"Error extracting feature info: {str(e)}")
#         return "our premium features"

def preload_company_data(company_id):
    """Preload company data into memory for faster access"""
    cursor = mysql.connection.cursor()
    try:
        # Get all company data in one query
        cursor.execute("""
            SELECT c.name, c.instructions, c.website, c.welcome_message,
                   MAX(cs.available_days), MAX(cs.start_time), MAX(cs.end_time),
                   GROUP_CONCAT(CONCAT(wp.url, '|', wp.page_title) SEPARATOR '||')
            FROM companies c
            LEFT JOIN company_settings cs ON c.id = cs.company_id
            LEFT JOIN website_pages wp ON c.id = wp.company_id
            WHERE c.id = %s
            GROUP BY c.id
        """, (company_id,))
        return cursor.fetchone()
    finally:
        cursor.close()

# Cache for company data
_company_data_cache = {}
_company_data_lock = threading.Lock()

def get_company_data(company_id):
    """Get company data from cache or load it"""
    if company_id not in _company_data_cache:
        with _company_data_lock:
            if company_id not in _company_data_cache:
                _company_data_cache[company_id] = preload_company_data(company_id)
    return _company_data_cache[company_id]

def get_document_content(vector_store, user_input):
    """Get relevant document content from vector store"""
    try:
        if vector_store and user_input:
            docs = vector_store.similarity_search_with_score(
                user_input,
                k=5
            )
            return "\n---\n".join([doc[0].page_content for doc in docs])
        return ""
    except Exception as e:
        logger.error(f"Error getting document content: {str(e)}")
        return ""

def format_settings_info(company_data):
    """Format company settings information"""
    try:
        if not company_data or not company_data[4]:  # available_days
            return "No meeting settings configured yet."
            
        available_days = company_data[4].split(',') if company_data[4] else []
        start_time = company_data[5]  # start_time
        end_time = company_data[6]    # end_time
        
        return f"""
        Meeting Availability:
        - Available Days: {', '.join(available_days)}
        - Time Slot: {start_time} to {end_time}
        """
    except Exception as e:
        logger.error(f"Error formatting settings info: {str(e)}")
        return "No meeting settings configured yet."

# def format_url_context(company_data):
#     """Format website URL context"""
#     try:
#         if not company_data or not company_data[7]:  # website_pages
#             return f"Main website: {company_data[2] if company_data else ''}\n"
            
#         url_context = "Available pages:\n"
#         website_pages = company_data[7].split('||')
#         for page in website_pages:
#             if page:
#                 url, title = page.split('|')
#                 url_context += f"- {title}: {url}\n"
#         return url_context
#     except Exception as e:
#         logger.error(f"Error formatting URL context: {str(e)}")
#         return ""

def get_current_segment(call_status, script_segments):
    """Determine current script segment based on call status"""
    try:
        if call_status == 'initial':
            return 0
        elif call_status == 'in_progress':
            return 1
        elif call_status == 'in_conversation':
            return 2
        else:
            return min(len(script_segments) - 1, int(call_status.split('_')[1]) if '_' in call_status else 0)
    except Exception as e:
        logger.error(f"Error getting current segment: {str(e)}")
        return 0

# def prepare_system_message(company_data, doc_content, url_context, settings_info, script_segments, current_segment):
#     """Prepare system message for the LLM"""
#     try:
#         company_name = company_data[0] if company_data else "the company"
#         company_instructions = company_data[1] if company_data else ""
        
#         return {
#             "role": "system",
#             "content": f"""You are a professional voice-based customer service representative for {company_name}. 
# You are currently in a phone call with a customer.

# Company Information:
# {company_instructions}

# Additional Information from Documents:
# {doc_content}

# Website Information:
# {url_context}

# {settings_info}

# Current Script Segment:
# {script_segments[current_segment] if current_segment < len(script_segments) else "End of script"}

# Previous Script Segments:
# {chr(10).join(script_segments[:current_segment]) if current_segment > 0 else "None"}

# Guidelines for Natural Conversation:
# 1. Speak naturally and conversationally, as if you're a real person
# 2. Follow the script segment but adapt it to sound natural
# 3. If the customer asks a question:
#    - Use the company information and documents to answer accurately
#    - If you don't know, say so politely
#    - Keep answers concise and relevant
# 4. If the customer doesn't respond:
#    - Wait a moment and then politely ask again
#    - If still no response, try rephrasing the question
#    - If multiple attempts fail, thank them and end the call
# 5. If the customer wants to end the call:
#    - Thank them politely
#    - Ask if they have any final questions
#    - Say goodbye professionally
# 6. Maintain a friendly and professional tone throughout
# 7. Don't mention that you're an AI or automated system
# 8. Use appropriate conversational fillers and transitions
# 9. If the customer seems confused, offer clarification
# 10. When asking for information, make it sound natural
# 11. When the customer provides information you requested (for example, you asked for their full name and they replied), always acknowledge their response with a brief confirmation such as "Got it, thank you", "Thank you for sharing that", or "Perfect, thanks for letting me know" before continuing the conversation.

# Remember:
# - This is a voice conversation - keep responses concise and easy to understand
# - Handle customer responses appropriately
# - If the customer wants to end the call, thank them and say goodbye
# - If you need to ask for information, do so naturally and conversationally
# - If the customer asks about something not in the script, use the company information to help
# """
#         }
#     except Exception as e:
#         logger.error(f"Error preparing system message: {str(e)}")
#         return {
#             "role": "system",
#             "content": "You are a professional customer service representative. Keep responses brief and helpful."
#         }

@lru_cache(maxsize=1000)
def get_cached_response(company_id, user_input, script, call_status):
    """Cache responses for common inputs"""
    key = f"{company_id}:{user_input}:{script}:{call_status}"
    return hashlib.md5(key.encode()).hexdigest()

# Add more common responses to the cache
COMMON_RESPONSES = {
    "greeting": "Hello! How can I help you today?",
    "farewell": "Thank you for your time. Have a great day!",
    "not_understood": "I apologize, but I didn't quite catch that. Could you please repeat?",
    "busy": "I understand you're busy. Would you prefer to schedule a call for another time?",
    "no_interest": "I understand. Thank you for your time. Have a great day!",
    "yes": "Great! Let me tell you more about that.",
    "no": "I understand. Let me explain why this might be beneficial for you.",
    "maybe": "I understand you're considering it. Let me provide more information.",
    "repeat": "Let me repeat that for you.",
    "confused": "Let me explain that more clearly.",
    "interrupt": "I understand. Let me continue with the important points.",
    "silence": "Are you still there? I'd be happy to continue our conversation.",
    "background_noise": "I'm having trouble hearing you clearly. Could you please repeat that?",
    "bad_connection": "I'm having trouble hearing you. Could you please speak a bit louder?",
    "hold_on": "I'll wait while you check that information.",
    "thank_you": "You're welcome! Is there anything else you'd like to know?",
    "good": "I'm glad to hear that! Let me tell you more about our services.",
    "bad": "I understand your concern. Let me address that specifically.",
    "price": "Let me explain our pricing structure.",
    "schedule": "I'd be happy to help you schedule a time that works for you.",
    "details": "Let me provide you with more specific information about that.",
}

def get_quick_response(user_input):
    """Get quick response for common inputs without LLM call"""
    if not user_input:  # Handle None or empty input
        return None
        
    user_input = user_input.lower().strip()
    
    # Check for greetings
    if any(word in user_input for word in ['hello', 'hi', 'hey']):
        return COMMON_RESPONSES['greeting']
    
    # Check for farewells
    if any(word in user_input for word in ['bye', 'goodbye', 'end call', 'hang up']):
        return COMMON_RESPONSES['farewell']
    
    # Check for busy signals
    if any(word in user_input for word in ['busy', 'not now', 'later', 'call back']):
        return COMMON_RESPONSES['busy']
    
    # Check for no interest
    if any(word in user_input for word in ['not interested', 'no thanks', 'no thank you']):
        return COMMON_RESPONSES['no_interest']
    
    # Check for yes/no responses
    if any(word in user_input for word in ['yes', 'yeah', 'sure', 'okay', 'ok']):
        return COMMON_RESPONSES['yes']
    
    if any(word in user_input for word in ['no', 'nope', 'nah']):
        return COMMON_RESPONSES['no']
    
    if any(word in user_input for word in ['maybe', 'perhaps', 'possibly']):
        return COMMON_RESPONSES['maybe']
    
    # Check for common conversation patterns
    if any(word in user_input for word in ['repeat', 'again', 'what did you say']):
        return COMMON_RESPONSES['repeat']
    
    if any(word in user_input for word in ['confused', 'don\'t understand', 'not clear']):
        return COMMON_RESPONSES['confused']
    
    if any(word in user_input for word in ['wait', 'hold on', 'one moment']):
        return COMMON_RESPONSES['hold_on']
    
    if any(word in user_input for word in ['thank you', 'thanks']):
        return COMMON_RESPONSES['thank_you']
    
    if any(word in user_input for word in ['good', 'great', 'excellent']):
        return COMMON_RESPONSES['good']
    
    if any(word in user_input for word in ['bad', 'terrible', 'awful']):
        return COMMON_RESPONSES['bad']
    
    if any(word in user_input for word in ['price', 'cost', 'how much']):
        return COMMON_RESPONSES['price']
    
    if any(word in user_input for word in ['schedule', 'appointment', 'meeting']):
        return COMMON_RESPONSES['schedule']
    
    if any(word in user_input for word in ['details', 'more information', 'tell me more']):
        return COMMON_RESPONSES['details']
    
    return None

def store_conversation_history(contact_id, call_sid, user_input, ai_response, call_status):
    """Store conversation history in the database"""
    try:
        with get_db_connection() as cursor:
            cursor.execute("""
                INSERT INTO call_conversation_history 
                (contact_id, call_sid, user_input, ai_response, call_status, created_at)
                VALUES (%s, %s, %s, %s, %s, NOW())
            """, (contact_id, call_sid, user_input, ai_response, call_status))
    except Exception as e:
        logger.error(f"Error storing conversation history: {str(e)}")

def get_conversation_history(contact_id, call_sid=None, limit=None):
    """Retrieve recent conversation history"""
    try:
        with get_db_connection() as cursor:
            if call_sid:
                if limit:
                    cursor.execute("""
                        SELECT user_input, ai_response, call_status
                        FROM call_conversation_history
                        WHERE call_sid = %s
                        ORDER BY created_at DESC
                        LIMIT %s
                    """, (call_sid, limit))
                else:
                    cursor.execute("""
                        SELECT user_input, ai_response, call_status
                        FROM call_conversation_history
                        WHERE call_sid = %s
                    """, (call_sid,))
            else:
                cursor.execute("""
                    SELECT user_input, ai_response, call_status
                    FROM call_conversation_history
                    WHERE contact_id = %s
                    ORDER BY created_at DESC
                """, (contact_id,))
            return cursor.fetchall()
    except Exception as e:
        logger.error(f"Error retrieving conversation history: {str(e)}")
        return []

def reset_connections():
    """
    Reset database connections if needed
    """
    try:
        with get_db_connection() as cursor:
            cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
            result = cursor.fetchone()
            if result and int(result[1]) > 80:  # If more than 80% of max connections
                cursor.execute("FLUSH HOSTS")
                logger.info("Database connections reset")
    except Exception as e:
        logger.error(f"Error resetting connections: {str(e)}")

def is_segment_fulfilled(script_segment, user_reply):
    """
    Use LLM to check if the user reply fulfills the script segment's request.
    Returns True if fulfilled, False otherwise.
    """
    prompt = (
        f"You are an assistant helping to automate phone calls. "
        f"Given the script segment: '{script_segment}' and the user's reply: '{user_reply}', "
        f"did the user provide the information or answer requested by the script segment? "
        f"Reply only with 'yes' or 'no'."
    )
    response = requests.post(
        Config.GROQ_API_URL,
        headers={
            "Authorization": f"Bearer {Config.GROQ_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "model": "meta-llama/llama-4-scout-17b-16e-instruct",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0,
            "max_tokens": 3,
            "top_p": 1,
            "stream": False
        },
        timeout=1.0
    )
    if response.status_code == 200:
        answer = response.json()["choices"][0]["message"]["content"].strip().lower()
        return answer.startswith("yes")
    return False  # fallback: don't advance

def add_human_pauses(text):
    fillers = ["hmm", "ah", "let me think", "okay", "sure", "well", "so", "I see", "right", "alright"]
    for filler in fillers:
        text = text.replace(f"{filler},", f"{filler}, <break time='400ms'/>")
    sentences = text.split('. ')
    text = '. <break time="300ms"/> '.join(sentences)
    return text

def strip_ssml(text):
    # Remove all <break .../> and other SSML tags
    return re.sub(r'<[^>]+>', '', text)

def normalize_email_from_speech(text):
    """
    Attempt to reconstruct a valid email address from speech-to-text output
    where the user spells out their email address.
    """
    if not text:
        return text

    # Lowercase for normalization
    text = text.lower()

    # Replace common speech-to-text misrecognitions
    text = text.replace(" at the rate ", " @ ")
    text = text.replace(" at the red ", " @ ")
    text = text.replace(" at ", " @ ")
    text = text.replace(" dot ", " . ")
    text = text.replace(" underscore ", " _ ")
    text = text.replace(" dash ", " - ")
    text = text.replace(" hyphen ", " - ")
    text = text.replace(" space ", "")
    text = text.replace(" period ", " . ")

    # Remove extra spaces
    text = re.sub(r"\s+", " ", text).strip()

    # If the text is a sequence of single letters/numbers separated by spaces, join them
    # e.g., "z s h a r i f 3 6 @ gmail . com"
    # Only do this if it looks like an email (contains @ and .)
    if "@" in text and "." in text:
        # Try to join single letters/numbers
        parts = text.split()
        email_parts = []
        i = 0
        while i < len(parts):
            # If this part is a single character (letter or digit), and the next part is also, join them
            if (len(parts[i]) == 1 and (parts[i].isalpha() or parts[i].isdigit())):
                # Collect all consecutive single chars
                start = i
                while i < len(parts) and len(parts[i]) == 1 and (parts[i].isalpha() or parts[i].isdigit()):
                    i += 1
                email_parts.append("".join(parts[start:i]))
            else:
                email_parts.append(parts[i])
                i += 1
        text = "".join([
            ("" if part in "@._-" else "") + part
            if part in "@._-" else part
            for part in email_parts
        ])
        # Remove spaces again
        text = text.replace(" ", "")

    # Final basic email validation
    if re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", text):
        return text
    return text  # Return as-is if not a valid email, so LLM can still see what user said

