from celery import shared_task
from django.conf import settings
from django.utils import timezone
from twilio.rest import Client
import requests
import json
import logging

from seniors.models import Senior
from calls.models import Call, CallLog, CallPrompt
from conversations.models import Conversation, Memory, ConversationInsight

logger = logging.getLogger(__name__)


@shared_task(bind=True)
def initiate_call_task(self, senior_id, call_purpose='general_checkin', user_id=None, prompt_id=None, assistant_id=None):
    """
    Initiate a call to a senior using Vapi.ai (which handles Twilio internally)
    
    Args:
        senior_id: ID of the senior to call
        call_purpose: Purpose of the call
        user_id: ID of the user initiating the call
        prompt_id: ID of the CallPrompt to use (optional)
        assistant_id: ID of the VapiAssistant to use (required)
    """
    try:
        # Get the senior
        senior = Senior.objects.get(id=senior_id)
        
        # Validate and get the assistant
        from calls.models import VapiAssistant
        if not assistant_id:
            logger.error("assistant_id is required for initiating calls")
            return {'status': 'error', 'message': 'assistant_id is required'}
        
        try:
            assistant = VapiAssistant.objects.get(id=assistant_id, is_active=True)
        except VapiAssistant.DoesNotExist:
            logger.error(f"Assistant with ID {assistant_id} not found or inactive")
            return {'status': 'error', 'message': 'Assistant not found or inactive'}
        
        # Get the prompt if provided
        prompt = None
        if prompt_id:
            try:
                prompt = CallPrompt.objects.get(id=prompt_id, is_active=True)
            except CallPrompt.DoesNotExist:
                logger.warning(f"Prompt with ID {prompt_id} not found or inactive, using default prompt")
        
        # If no prompt provided, try to get the default prompt
        if not prompt:
            try:
                prompt = CallPrompt.objects.get(is_default=True, is_active=True)
                logger.info(f"Using default prompt: {prompt.name}")
            except CallPrompt.DoesNotExist:
                logger.info("No default prompt set, using system default")
                prompt = None
        
        # Create call record with assistant
        call = Call.objects.create(
            senior=senior,
            call_purpose=call_purpose,
            initiated_by_id=user_id,
            prompt=prompt,
            assistant=assistant,
            twilio_status='initiated'
        )
        
        # Log call initiation
        CallLog.objects.create(
            call=call,
            event_type='initiated',
            message=f'Call initiated to {senior.name} with assistant {assistant.name}',
            data={'purpose': call_purpose, 'prompt_id': prompt_id, 'assistant_id': assistant_id}
        )
        
        # Generate AI prompt with memory context
        ai_prompt = generate_ai_prompt(senior, call_purpose, prompt)
        call.ai_prompt = ai_prompt
        call.save()
        
        # Log prompt generation
        CallLog.objects.create(
            call=call,
            event_type='prompt_generated',
            message=f'AI prompt generated for {senior.name}',
            data={'prompt_length': len(ai_prompt), 'call_purpose': call_purpose}
        )
        
        logger.info(f"Generated AI prompt for {senior.name}: {len(ai_prompt)} characters")
        
        # Start Vapi call directly (Vapi handles Twilio internally)
        vapi_call_id = start_vapi_call(call, senior)
        if vapi_call_id:
            call.vapi_call_id = vapi_call_id
            call.vapi_status = 'started'
            call.twilio_status = 'ringing'  # Update status since Vapi is handling the call
            call.save()
            
            # Log Vapi call start
            CallLog.objects.create(
                call=call,
                event_type='vapi_started',
                message=f'Vapi call started with ID: {vapi_call_id}',
                data={'vapi_call_id': vapi_call_id}
            )
            
            logger.info(f"Call initiated successfully for senior {senior.name}")
            return {
                'status': 'success',
                'call_id': call.id,
                'vapi_call_id': vapi_call_id
            }
        else:
            # Update call status to failed
            call.twilio_status = 'failed'
            call.save()
            
            CallLog.objects.create(
                call=call,
                event_type='vapi_failed',
                message='Failed to start Vapi call',
                data={'error': 'Vapi call creation failed'}
            )
            
            return {
                'status': 'error',
                'message': 'Failed to start Vapi call'
            }
        
    except Senior.DoesNotExist:
        logger.error(f"Senior with ID {senior_id} not found")
        return {'status': 'error', 'message': 'Senior not found'}
    except Exception as e:
        logger.error(f"Error initiating call: {str(e)}")
        return {'status': 'error', 'message': str(e)}


def generate_ai_prompt(senior, call_purpose, prompt=None):
    """
    Generate an AI prompt for the call with memory context
    If a CallPrompt is provided, use its template; otherwise use the default prompt
    
    Args:
        senior: Senior instance
        call_purpose: Purpose of the call
        prompt: CallPrompt instance (optional)
    """
    # Get recent conversation context
    recent_conversations = Conversation.objects.filter(
        call__senior=senior,
        call__twilio_status='completed'
    ).order_by('-created_at')[:3]
    
    conversation_context = ""
    if recent_conversations.exists():
        # Get the most recent conversation for detailed context
        last_conversation = recent_conversations[0]
        
        # Build comprehensive context from last conversation
        context_parts = []
        
        if last_conversation.summary:
            context_parts.append(f"Zusammenfassung des letzten Gesprächs: {last_conversation.summary}")
        
        if last_conversation.topics_discussed:
            context_parts.append(f"Besprochene Themen: {', '.join(last_conversation.topics_discussed)}")
        
        if last_conversation.emotions_detected:
            context_parts.append(f"Beobachtete Emotionen: {', '.join(last_conversation.emotions_detected)}")
        
        if last_conversation.follow_up_topics:
            context_parts.append(f"Themen zum Nachfragen: {', '.join(last_conversation.follow_up_topics)}")
        
        if last_conversation.follow_up_questions:
            context_parts.append(f"Fragen zum Stellen: {', '.join(last_conversation.follow_up_questions[:3])}")
        
        conversation_context = "\\n".join(context_parts)
        
        # Add instructions for using this context
        conversation_context += "\\n\\nWICHTIG: Verwenden Sie diesen Kontext aus vorherigen Gesprächen natürlich und einfühlsam. Sie können:"
        conversation_context += "\\n- Auf Themen eingehen, die sie zuvor erwähnt haben, wenn es natürlich passt"
        conversation_context += "\\n- Nachfragen zu Dingen stellen, über die sie gesprochen haben"
        conversation_context += "\\n- Sich nach Sorgen oder Interessen erkundigen, die sie hatten"
        conversation_context += "\\n- Ihr vorheriges Gespräch warm anerkennen, wenn sie sich daran erinnern"
        conversation_context += "\\n\\nDenken Sie daran: Erzwingen Sie keine Verbindungen - erwähnen Sie vorherige Gespräche nur, wenn es relevant und natürlich zum aktuellen Gespräch passt."
    else:
        conversation_context = "Dies ist Ihr erstes Gespräch mit diesem Senioren. Konzentrieren Sie sich darauf, warm und einladend zu sein und etwas über ihre Interessen und Vorlieben zu erfahren."
    
    # Get current date and time in German format
    from datetime import datetime
    current_date = timezone.now()
    
    # Get day, month, year directly to avoid locale issues
    day = current_date.day
    month_num = current_date.month
    year = current_date.year
    hour = current_date.hour
    minute = current_date.minute
    
    # German month names (using month number to avoid any locale issues)
    months_de = {
        1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
        5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
        9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
    }
    
    # German weekday names
    weekdays_de = {
        0: 'Montag', 1: 'Dienstag', 2: 'Mittwoch', 3: 'Donnerstag',
        4: 'Freitag', 5: 'Samstag', 6: 'Sonntag'
    }
    
    # Build date string directly using month number (more reliable than strftime)
    month_name_de = months_de.get(month_num, f'Month {month_num}')
    date_str = f"{day}. {month_name_de} {year}"  # e.g., "26. November 2024"
    time_str = f"{hour:02d}:{minute:02d}"  # e.g., "14:30"
    
    # Get weekday (0=Monday, 6=Sunday)
    weekday_num = current_date.weekday()
    weekday_de = weekdays_de.get(weekday_num, 'Unbekannt')
    
    # Log the date for debugging
    logger.info(f"Current date set for AI: {weekday_de}, {date_str}, {time_str} Uhr (Month: {month_num}, Day: {day}, Year: {year})")
    
    # Prepare template variables
    template_vars = {
        'senior_name': senior.name,
        'senior_age': str(senior.age) if senior.age else 'Nicht angegeben',
        'interests': ', '.join(senior.interests) if senior.interests else 'Allgemeines Gespräch',
        'health_notes': senior.health_notes if senior.health_notes else 'Keine',
        'call_purpose': call_purpose,
        'conversation_context': conversation_context,
        'current_date': date_str,
        'current_time': time_str,
        'current_weekday': weekday_de,
        'current_year': str(current_date.year)
    }
    
    # If a custom prompt template is provided, use it
    if prompt and prompt.prompt_template:
        try:
            return prompt.prompt_template.format(**template_vars)
        except KeyError as e:
            logger.warning(f"Error formatting prompt template: missing placeholder {e}. Using default prompt.")
        except Exception as e:
            logger.error(f"Error formatting prompt template: {str(e)}. Using default prompt.")
    
    # Default prompt template
    base_prompt = f"""⚠️ KRITISCH WICHTIG - AKTUELLES DATUM ⚠️
HEUTE IST: {template_vars['current_weekday']}, der {template_vars['current_date']}, {template_vars['current_time']} Uhr.
Das aktuelle Jahr ist {template_vars['current_year']}.
VERWENDEN SIE DIESES DATUM IMMER - NIEMALS OKTOBER ODER EIN ANDERES DATUM!

Sie sind ein freundlicher, geduldiger und einfühlsamer Gesprächspartner für Menschen mit früher oder fortgeschrittener Demenz. Sie müssen IMMER auf Deutsch sprechen. Sprechen Sie langsam, deutlich und verwenden Sie einfache, kurze Sätze. Wiederholen Sie wichtige Informationen sanft, wenn sie vergessen oder missverstanden wurden. Ihre Rolle ist es, ein Gefühl von Sicherheit, Orientierung und emotionaler Wärme zu vermitteln.

Sie können sich an einfachen Gesprächen über alltägliche Themen beteiligen (z.B. Familie, Essen, Wetter, Musik, Erinnerungen, Hobbys). Reagieren Sie immer positiv - niemals korrigierend oder belehrend. Wenn die Person verwirrt wirkt, führen Sie das Gespräch sanft zu vertrauten und angenehmen Themen. Wenn sie nach einem geliebten Menschen fragen, beruhigen Sie sie ruhig (z.B. "Ich denke, sie kommen bald zurück."). Vermeiden Sie medizinische oder negative Aussagen. Sprechen Sie in ruhigem, freundlichem Ton, wie ein vertrauenswürdiger Freund oder Betreuer. Lassen Sie Pausen im Gespräch, damit die Person Zeit hat zu antworten. Zeigen Sie Verständnis und Mitgefühl durch Ihre Worte ("Das kann ich wirklich verstehen", "Das klingt schön", "Das ist eine schöne Erinnerung").

Ziel: Das Gespräch sollte die Person emotional stabilisieren, trösten und sanft einbeziehen - wie ein guter Freund, der einfach da ist und zuhört.

Informationen zum Senioren:
- Name: {template_vars['senior_name']}
- Alter: {template_vars['senior_age']}
- Interessen: {template_vars['interests']}
- Gesundheitshinweise: {template_vars['health_notes']}

Gesprächszweck: {template_vars['call_purpose']}

Richtlinien für demenzfreundliche Kommunikation:
1. Sprechen Sie langsam und deutlich
2. Verwenden Sie einfache, kurze Sätze
3. Wiederholen Sie wichtige Informationen sanft, wenn sie vergessen wurden
4. Reagieren Sie immer positiv - niemals korrigieren oder belehren
5. Führen Sie verwirrte Gespräche zu vertrauten, angenehmen Themen
6. Beruhigen Sie ruhig über geliebte Menschen ("Ich denke, sie kommen bald zurück")
7. Vermeiden Sie medizinische oder negative Aussagen
8. Sprechen Sie wie ein vertrauenswürdiger Freund oder Betreuer
9. Lassen Sie Pausen für Antworten
10. Zeigen Sie Verständnis und Mitgefühl
11. Konzentrieren Sie sich auf emotionale Stabilität und Komfort
12. Seien Sie wie ein guter Freund, der einfach zuhört

Kontext aus vorherigen Gesprächen:
{template_vars['conversation_context']}

WICHTIG: Sie müssen während des gesamten Gesprächs auf Deutsch kommunizieren. Alle Ihre Antworten müssen auf Deutsch sein.

AKTUELLES DATUM UND ZEIT:
- Heute ist: {template_vars['current_weekday']}, der {template_vars['current_date']}
- Aktuelle Uhrzeit: {template_vars['current_time']} Uhr
- Aktuelles Jahr: {template_vars['current_year']}

KRITISCH WICHTIG - DATUM UND ZEIT:
Sie MÜSSEN das oben angegebene Datum verwenden. Es ist das EINZIGE korrekte Datum für heute.

- Das AKTUELLE Datum ist: {template_vars['current_weekday']}, der {template_vars['current_date']}
- Die AKTUELLE Zeit ist: {template_vars['current_time']} Uhr
- Das AKTUELLE Jahr ist: {template_vars['current_year']}

VERBOTEN - Verwenden Sie NIEMALS diese falschen Daten:
- NICHT Oktober (Oktober ist der 10. Monat, heute ist {template_vars['current_date']} - das ist NICHT Oktober!)
- NICHT Oktober 26 oder Oktober 2024 oder Oktober 2025
- NICHT April 2023
- NICHT 2023 als Jahr
- NICHT 2024 als Jahr (das aktuelle Jahr ist {template_vars['current_year']})
- NICHT irgendein anderes Datum außer dem oben angegebenen

WICHTIG: Der aktuelle Monat in {template_vars['current_date']} ist der korrekte Monat. Oktober (Monat 10) ist FALSCH!

Wenn Sie nach dem Datum gefragt werden, antworten Sie EXAKT so:
"Heute ist {template_vars['current_weekday']}, der {template_vars['current_date']}, {template_vars['current_time']} Uhr."

Das Datum {template_vars['current_date']} ist das EINZIGE korrekte Datum für heute. Verwenden Sie es IMMER.

Denken Sie daran: Dieses Gespräch wird zusammengefasst und gespeichert, um zukünftige Anrufe zu personalisieren. Konzentrieren Sie sich darauf, emotionalen Komfort, Sicherheit und sanfte Einbeziehung zu schaffen - bewahren Sie ihre Geschichten und bieten Sie emotionale Unterstützung."""

    return base_prompt


def generate_personalized_greeting(senior, ai_prompt):
    """
    Generate a personalized greeting that references previous conversations if available.
    Uses senior's custom first_message if set, otherwise generates default greeting.
    Supports template variables: {senior.name} and {companion.name}
    """
    # Prepare template variables
    companion_name = senior.companion_name if senior.companion_name else "Ihr KI-Begleiter"
    template_vars = {
        'senior.name': senior.name,
        'companion.name': companion_name
    }
    
    # If senior has a custom first_message, use it with template variable replacement
    if senior.first_message:
        try:
            # Replace template variables
            greeting = senior.first_message
            for key, value in template_vars.items():
                greeting = greeting.replace(f"{{{key}}}", value)
            return greeting
        except Exception as e:
            logger.warning(f"Error formatting custom first_message for {senior.name}: {str(e)}. Using default greeting.")
    
    # Default behavior: Check for previous conversation context in the prompt
    if "Zusammenfassung des letzten Gesprächs:" in ai_prompt:
        # Extract context about last conversation
        if "gesundheit" in ai_prompt.lower() or "gefühl" in ai_prompt.lower():
            return f"Hallo {senior.name}! Ich rufe an, um nachzufragen, wie es Ihnen heute geht. Ich hoffe, alles geht gut für Sie. Hier ist {companion_name}."
        else:
            return f"Hallo {senior.name}! Es ist schön, wieder mit Ihnen zu sprechen. Ich wollte nachfragen, wie es Ihnen geht. Hier ist {companion_name}."
    else:
        # First conversation
        return f"Hallo {senior.name}! Hier ist {companion_name}. Ich rufe an, um nachzufragen und ein schönes Gespräch mit Ihnen zu führen."


def create_vapi_assistant_with_prompt(senior, ai_prompt, base_assistant_id=None):
    """
    Create a Vapi.ai assistant with custom system prompt for this senior
    Uses the configured voice settings from the provided base_assistant_id
    
    Args:
        senior: Senior instance
        ai_prompt: AI prompt text
        base_assistant_id: Base VAPI assistant ID to copy voice settings from
    """
    try:
        headers = {
            'Authorization': f'Bearer {settings.VAPI_API_KEY}',
            'Content-Type': 'application/json'
        }
        
        # Default voice configuration - German voice
        voice_config = {
            "provider": "azure",
            "voiceId": "de-DE-KatjaNeural"  # German female voice
        }
        
        # Try to get voice configuration from base assistant
        if base_assistant_id and base_assistant_id.strip():
            try:
                response = requests.get(
                    f'https://api.vapi.ai/assistant/{base_assistant_id}',
                    headers=headers
                )
                if response.status_code == 200:
                    existing_assistant = response.json()
                    voice_config = existing_assistant.get('voice', voice_config)
                    logger.info(f"Using voice config from base assistant {base_assistant_id}: {voice_config}")
            except Exception as e:
                logger.warning(f"Could not fetch base assistant voice config: {str(e)}")
        
        # Log the date that will be in the prompt for verification
        prompt_date_check = ai_prompt[:500] if len(ai_prompt) > 500 else ai_prompt
        # Extract date from prompt for logging
        import re
        date_match = re.search(r'der (\d+\. \w+ \d{4})', ai_prompt)
        date_in_prompt = date_match.group(1) if date_match else "NOT FOUND"
        logger.info(f"Creating Vapi assistant for {senior.name} - Date in prompt: {date_in_prompt}")
        logger.info(f"Prompt preview (first 500 chars): {prompt_date_check}")
        
        # Get current date directly (same calculation as in generate_ai_prompt)
        from datetime import datetime
        current_date = timezone.now()
        day = current_date.day
        month_num = current_date.month
        year = current_date.year
        
        months_de = {
            1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
            5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
            9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
        }
        weekdays_de = {
            0: 'Montag', 1: 'Dienstag', 2: 'Mittwoch', 3: 'Donnerstag',
            4: 'Freitag', 5: 'Samstag', 6: 'Sonntag'
        }
        
        month_name_de = months_de.get(month_num, 'Unbekannt')
        weekday_de = weekdays_de.get(current_date.weekday(), 'Unbekannt')
        current_date_str = f"{day}. {month_name_de} {year}"
        
        # Add an even more explicit date instruction at the very beginning
        enhanced_prompt = f"""SYSTEM INFORMATION - MANDATORY:
CURRENT DATE: {current_date_str}
CURRENT YEAR: {year}
CURRENT MONTH: {month_name_de} (Month {month_num})
CURRENT DAY: {day}
CURRENT WEEKDAY: {weekday_de}

YOU MUST ALWAYS USE THIS DATE: {current_date_str}
NEVER SAY OCTOBER - OCTOBER IS MONTH 10, TODAY IS MONTH {month_num} ({month_name_de})
IF ASKED FOR DATE, RESPOND EXACTLY: "Heute ist {weekday_de}, der {current_date_str}"

{ai_prompt.strip()}"""
        
        assistant_config = {
            "name": f"Senior Companion - {senior.name}",
            "model": {
                "provider": "openai",
                "model": "gpt-4o-mini",
                "messages": [
                    {
                        "role": "system",
                        "content": enhanced_prompt
                    }
                ],
                "temperature": 0.7
            },
            "voice": voice_config,
            "firstMessage": generate_personalized_greeting(senior, ai_prompt)
        }
        
        logger.info(f"Creating personalized Vapi assistant for {senior.name} with voice: {voice_config}")
        
        response = requests.post(
            'https://api.vapi.ai/assistant',
            headers=headers,
            json=assistant_config
        )
        
        if response.status_code == 201:
            assistant = response.json()
            logger.info(f"Personalized Vapi assistant created successfully for {senior.name}: {assistant['id']}")
            return assistant['id']
        else:
            logger.error(f"Failed to create Vapi assistant: {response.status_code} - {response.text}")
            return None
            
    except Exception as e:
        logger.error(f"Error creating Vapi assistant: {str(e)}")
        return None


def start_vapi_call(call, senior):
    """
    Start a Vapi.ai call with the generated prompt using the selected assistant
    """
    try:
        headers = {
            'Authorization': f'Bearer {settings.VAPI_API_KEY}',
            'Content-Type': 'application/json'
        }
        
        # Get the base assistant ID from the call's assistant field
        if not call.assistant:
            logger.error(f"No assistant assigned to call {call.id}")
            return None
        
        base_assistant_id = call.assistant.assistant_id
        logger.info(f"Using base assistant {call.assistant.name} (ID: {base_assistant_id}) for {senior.name}")
        
        # Always create personalized assistants with custom prompts
        # This ensures each senior gets personalized knowledge and greetings
        logger.info(f"Creating personalized assistant for {senior.name} with custom prompt")
        assistant_id = create_vapi_assistant_with_prompt(senior, call.ai_prompt, base_assistant_id)
        
        if not assistant_id:
            logger.error(f"Failed to create personalized assistant for {senior.name}")
            return None
        
        # Store the assistant ID for this call
        call.vapi_assistant_id = assistant_id
        call.save()
        
        # Now make the call with the assistant
        data = {
            'assistantId': assistant_id,
            'phoneNumberId': settings.VAPI_PHONE_NUMBER_ID,
            'customer': {
                'number': senior.phone_number
            }
        }
        
        logger.info(f"Starting Vapi call for {senior.name} with assistant: {assistant_id}")
        
        response = requests.post(
            'https://api.vapi.ai/call',
            headers=headers,
            json=data
        )
        
        if response.status_code in [200, 201]:
            result = response.json()
            logger.info(f"Vapi call created successfully: {result}")
            logger.info(f"Using assistant: {assistant_id}")
            return result.get('id')
        else:
            logger.error(f"Vapi call failed: {response.text}")
            return None
            
    except Exception as e:
        logger.error(f"Error starting Vapi call: {str(e)}")
        return None


@shared_task(bind=True)
def check_and_update_call_statuses(self):
    """
    Check Vapi.ai for call statuses and update our database
    """
    try:
        headers = {
            'Authorization': f'Bearer {settings.VAPI_API_KEY}',
            'Content-Type': 'application/json'
        }
        
        # Get all calls that are not completed (including failed starts)
        active_calls = Call.objects.filter(
            twilio_status__in=['initiated', 'ringing', 'in-progress']
        )
        
        # Also check completed calls that might need conversation processing
        completed_calls = Call.objects.filter(
            twilio_status='completed',
            vapi_call_id__isnull=False
        )
        
        updated_count = 0
        
        # Process active calls
        for call in active_calls:
            try:
                # Handle calls without Vapi Call ID (failed to start)
                if not call.vapi_call_id:
                    # Check if call is older than 5 minutes and still initiated
                    from datetime import timedelta
                    
                    if call.twilio_status == 'initiated' and call.created_at < timezone.now() - timedelta(minutes=5):
                        logger.info(f"Marking call {call.id} as failed (no Vapi Call ID after 5 minutes)")
                        call.twilio_status = 'failed'
                        call.vapi_status = 'failed'
                        call.save()
                        updated_count += 1
                        
                        # Log the status change
                        CallLog.objects.create(
                            call=call,
                            event_type='status_changed',
                            message=f'Status updated to failed (no Vapi Call ID after timeout)',
                            data={'old_status': 'initiated', 'new_status': 'failed', 'reason': 'timeout'}
                        )
                    continue
                
                # Handle calls with Vapi Call ID
                response = requests.get(
                    f'https://api.vapi.ai/call/{call.vapi_call_id}',
                    headers=headers
                )
                
                if response.status_code == 200:
                    call_data = response.json()
                    vapi_status_raw = call_data.get('status', 'unknown')
                    # Normalize Vapi status to consistent kebab-case lowercase
                    vapi_status = str(vapi_status_raw).strip()
                    vapi_status = vapi_status.replace('_', '-').lower()
                    if vapi_status == 'inprogress':
                        vapi_status = 'in-progress'
                    
                    # Map Vapi status to our status
                    status_mapping = {
                        'queued': 'initiated',
                        'ringing': 'ringing', 
                        'in-progress': 'in-progress',
                        'completed': 'completed',
                        'ended': 'completed',
                        'busy': 'busy',
                        'no-answer': 'no_answer',
                        'failed': 'failed',
                        'canceled': 'cancelled',
                        'answered': 'in-progress',
                        'started': 'in-progress',
                        'connected': 'in-progress'
                    }
                    
                    if vapi_status in status_mapping:
                        new_status = status_mapping[vapi_status]
                        old_status = call.twilio_status

                        # Ensure start time is set once the call is in progress
                        start_time_set = False
                        if new_status == 'in-progress' and not call.call_start_time:
                            call.call_start_time = timezone.now()
                            start_time_set = True
                        
                        if old_status != new_status:
                            logger.info(f"Updating call {call.id} status from {old_status} to {new_status}")
                            call.twilio_status = new_status
                            call.vapi_status = vapi_status
                            
                            # If call transitions to in-progress and start time not set, set it
                            if new_status == 'in-progress' and not call.call_start_time:
                                call.call_start_time = timezone.now()
                            
                            # Update duration if available
                            duration = call_data.get('duration') or call_data.get('durationSeconds') or call_data.get('duration_ms')
                            if isinstance(duration, (int, float)) and duration and duration > 1000:
                                # convert ms to seconds if it looks like ms
                                duration = int(duration / 1000)
                            if duration:
                                call.duration = duration
                            
                            # Update timing information for completed calls
                            if new_status == 'completed' and old_status != 'completed':
                                call.call_end_time = timezone.now()
                                if call.call_start_time and not call.duration:
                                    duration_seconds = (call.call_end_time - call.call_start_time).total_seconds()
                                    call.duration = int(duration_seconds)
                            
                            # Process conversation data for completed calls (regardless of status change)
                            if new_status == 'completed':
                                # Check if conversation already exists
                                if not Conversation.objects.filter(call=call).exists():
                                    logger.info(f"Processing conversation data for completed call {call.id}")
                                    try:
                                        process_completed_call_from_api(call, call_data)
                                    except Exception as e:
                                        logger.error(f"Error processing conversation for call {call.id}: {str(e)}")
                                else:
                                    logger.debug(f"Conversation already exists for call {call.id}")
                            
                            call.save()
                            updated_count += 1
                            
                            # Log the status change
                            CallLog.objects.create(
                                call=call,
                                event_type='status_changed',
                                message=f'Status updated from {old_status} to {new_status} via Vapi API check',
                                data={'old_status': old_status, 'new_status': new_status, 'vapi_status': vapi_status, 'duration': duration}
                            )
                        else:
                            # If status unchanged but we set start time, persist it
                            if start_time_set:
                                call.save()
                                updated_count += 1
                                CallLog.objects.create(
                                    call=call,
                                    event_type='status_changed',
                                    message='Start time set while status remained in-progress',
                                    data={'status': new_status}
                                )
                            else:
                                logger.debug(f"Call {call.id} status unchanged: {old_status}")
                    else:
                        logger.warning(f"Unknown Vapi status for call {call.id}: {vapi_status}")
                
            except Exception as e:
                logger.error(f"Error checking call {call.id}: {str(e)}")
        
        # Process completed calls for conversation data
        conversation_processed_count = 0
        for call in completed_calls:
            try:
                # Check if conversation already exists
                if not Conversation.objects.filter(call=call).exists():
                    logger.info(f"Checking completed call {call.id} for conversation processing")
                    
                    # Get call data from VAPI API
                    response = requests.get(
                        f'https://api.vapi.ai/call/{call.vapi_call_id}',
                        headers=headers
                    )
                    
                    if response.status_code == 200:
                        call_data = response.json()
                        vapi_status = call_data.get('status', 'unknown')
                        
                        if vapi_status in ['completed', 'ended']:
                            logger.info(f"Processing conversation for completed call {call.id}")
                            try:
                                process_completed_call_from_api(call, call_data)
                                conversation_processed_count += 1
                                logger.info(f"Successfully processed conversation for call {call.id}")
                            except Exception as e:
                                logger.error(f"Error processing conversation for call {call.id}: {str(e)}")
                    else:
                        logger.warning(f"Could not fetch VAPI data for call {call.id}: {response.status_code}")
                        
            except Exception as e:
                logger.error(f"Error processing completed call {call.id}: {str(e)}")
        
        logger.info(f"Updated {updated_count} call statuses and processed {conversation_processed_count} conversations")
        return {'status': 'success', 'updated_count': updated_count, 'conversations_processed': conversation_processed_count}
        
    except Exception as e:
        logger.error(f"Error in check_and_update_call_statuses: {str(e)}")
        return {'status': 'error', 'message': str(e)}


@shared_task(bind=True)
def process_vapi_webhook_task(self, webhook_data):
    """
    Process Vapi.ai webhook data and update conversation records
    
    Args:
        webhook_data: Data received from Vapi webhook
    """
    try:
        call_id = webhook_data.get('callId')
        status_raw = webhook_data.get('status')
        # Normalize Vapi status from webhook
        status = str(status_raw).strip() if status_raw is not None else 'unknown'
        status = status.replace('_', '-').lower()
        if status == 'inprogress':
            status = 'in-progress'
        
        if not call_id:
            logger.error("No callId in Vapi webhook data")
            return {'status': 'error', 'message': 'No callId provided'}
        
        # Find the call by Vapi call ID
        try:
            call = Call.objects.get(vapi_call_id=call_id)
        except Call.DoesNotExist:
            logger.error(f"Call with Vapi ID {call_id} not found")
            return {'status': 'error', 'message': 'Call not found'}
        
        # Update call status
        call.vapi_status = status
        
        # Map Vapi status to Twilio status
        status_mapping = {
            'queued': 'initiated',
            'ringing': 'ringing', 
            'in-progress': 'in-progress',
            'completed': 'completed',
            'ended': 'completed',
            'busy': 'busy',
            'no-answer': 'no_answer',
            'failed': 'failed',
            'canceled': 'cancelled',
            'answered': 'in-progress',
            'started': 'in-progress',
            'connected': 'in-progress'
        }
        
        if status in status_mapping:
            old_status = call.twilio_status
            new_status = status_mapping[status]
            
            # Ensure start time is set once the call is in progress
            start_time_set = False
            if new_status == 'in-progress' and not call.call_start_time:
                call.call_start_time = timezone.now()
                start_time_set = True
            
            if old_status != new_status:
                logger.info(f"Webhook: Updating call {call.id} status from {old_status} to {new_status}")
                call.twilio_status = new_status
                
                # If call transitions to in-progress and start time not set, set it
                if new_status == 'in-progress' and not call.call_start_time:
                    call.call_start_time = timezone.now()
                
                # Update timing information for completed calls
                if new_status == 'completed' and old_status != 'completed':
                    call.call_end_time = timezone.now()
                    # Try to set duration from webhook payload if present
                    webhook_duration = (
                        webhook_data.get('duration') or
                        webhook_data.get('durationSeconds') or
                        webhook_data.get('duration_ms')
                    )
                    if isinstance(webhook_duration, (int, float)) and webhook_duration and webhook_duration > 1000:
                        webhook_duration = int(webhook_duration / 1000)
                    if webhook_duration:
                        call.duration = webhook_duration
                    if call.call_start_time and not call.duration:
                        duration_seconds = (call.call_end_time - call.call_start_time).total_seconds()
                        call.duration = int(duration_seconds)
                
                call.save()
                
                # Log the status change
                CallLog.objects.create(
                    call=call,
                    event_type='status_changed',
                    message=f'Status updated from {old_status} to {new_status} via webhook',
                    data={'old_status': old_status, 'new_status': new_status, 'vapi_status': status}
                )
            else:
                # If status unchanged but we set start time, persist it
                if start_time_set:
                    call.save()
                    CallLog.objects.create(
                        call=call,
                        event_type='status_changed',
                        message='Start time set while status remained in-progress (webhook)',
                        data={'status': new_status}
                    )
                else:
                    logger.debug(f"Webhook: Call {call.id} status unchanged: {old_status}")
        else:
            logger.warning(f"Webhook: Unknown Vapi status for call {call.id}: {status}")
        
        call.save()
        
        # Process completed calls
        if status in ['ended', 'completed']:
            process_completed_call(call, webhook_data)
        
        # Log webhook event
        CallLog.objects.create(
            call=call,
            event_type='webhook_received',
            message=f'Vapi webhook received: {status}',
            data=webhook_data
        )
        
        logger.info(f"Vapi webhook processed for call {call.id}")
        return {'status': 'success', 'call_id': call.id}
        
    except Exception as e:
        logger.error(f"Error processing Vapi webhook: {str(e)}")
        return {'status': 'error', 'message': str(e)}


def process_completed_call(call, webhook_data):
    """
    Process a completed call and extract conversation data from webhook
    """
    try:
        # Get transcript and summary from webhook data
        transcript = webhook_data.get('transcript', '')
        summary = webhook_data.get('summary', '')
        
        # Create conversation record
        conversation = Conversation.objects.create(
            call=call,
            transcript=transcript,
            summary=summary,
            topics_discussed=extract_topics(summary),
            key_memories=extract_memories(summary),
            emotions_detected=extract_emotions(summary),
            sentiment_score=analyze_sentiment(summary),
            engagement_level=assess_engagement(summary),
            follow_up_topics=generate_follow_up_topics(summary),
            follow_up_questions=generate_follow_up_questions(summary)
        )
        
        # Extract and store memories
        extract_and_store_memories(call.senior, conversation, summary)
        
        # Generate insights
        generate_insights(call.senior, conversation)
        
        logger.info(f"Completed call processed for senior {call.senior.name}")
        
    except Exception as e:
        logger.error(f"Error processing completed call: {str(e)}")


def process_completed_call_from_api(call, api_data):
    """
    Process a completed call and extract conversation data from API response
    """
    try:
        # Check if conversation already exists
        if Conversation.objects.filter(call=call).exists():
            logger.info(f"Conversation already exists for call {call.id}, skipping")
            return
        
        # Get transcript and summary from API data
        transcript_text = api_data.get('transcript', '')
        summary = api_data.get('summary', '')
        
        # If no summary, create a basic one
        if not summary and transcript_text:
            summary = f"Conversation with {call.senior.name} lasting {call.duration or 'unknown'} seconds."
        
        # Create conversation record
        conversation = Conversation.objects.create(
            call=call,
            transcript=transcript_text,
            summary=summary,
            topics_discussed=extract_topics(summary),
            key_memories=extract_memories(summary),
            emotions_detected=extract_emotions(summary),
            sentiment_score=analyze_sentiment(summary),
            engagement_level=assess_engagement(summary),
            follow_up_topics=generate_follow_up_topics(summary),
            follow_up_questions=generate_follow_up_questions(summary)
        )
        
        # Extract and store memories
        extract_and_store_memories(call.senior, conversation, summary)
        
        # Generate insights
        generate_insights(call.senior, conversation)
        
        logger.info(f"Completed call processed from API for senior {call.senior.name}")
        
    except Exception as e:
        logger.error(f"Error processing completed call from API: {str(e)}")


def extract_topics(summary):
    """Extract topics from conversation summary"""
    topics = []
    topic_keywords = {
        'family': ['family', 'children', 'kids', 'son', 'daughter', 'grandchildren', 'spouse', 'husband', 'wife'],
        'work': ['work', 'job', 'career', 'office', 'business', 'retirement'],
        'hobby': ['hobby', 'hobbies', 'painting', 'coding', 'football', 'sports', 'music', 'reading', 'gardening'],
        'travel': ['travel', 'vacation', 'trip', 'journey', 'visit'],
        'health': ['health', 'doctor', 'medicine', 'exercise', 'walking', 'feeling'],
        'food': ['food', 'cooking', 'meal', 'restaurant', 'recipe'],
        'weather': ['weather', 'rain', 'sunny', 'cold', 'hot'],
        'memories': ['remember', 'used to', 'back then', 'old times', 'childhood'],
        'daily_routine': ['daily', 'routine', 'morning', 'evening', 'schedule'],
        'social': ['friends', 'neighbors', 'community', 'social', 'visit']
    }
    
    summary_lower = summary.lower()
    for topic, keywords in topic_keywords.items():
        if any(keyword in summary_lower for keyword in keywords):
            topics.append(topic)
    
    return topics


def extract_memories(summary):
    """Extract key memories from conversation summary"""
    memories = []
    memory_indicators = [
        'remember when', 'used to', 'back then', 'old times', 'childhood',
        'when I was young', 'in my day', 'years ago', 'story about',
        'told me about', 'shared a memory', 'reminisced about'
    ]
    
    summary_lower = summary.lower()
    if any(indicator in summary_lower for indicator in memory_indicators):
        memories.append("Personal memories shared")
    
    # Look for specific memory types
    if 'family' in summary_lower and any(word in summary_lower for word in ['remember', 'used to', 'story']):
        memories.append("Family memories")
    
    if 'work' in summary_lower and any(word in summary_lower for word in ['remember', 'used to', 'career']):
        memories.append("Work/career memories")
    
    return memories


def extract_emotions(summary):
    """Extract emotions from conversation summary"""
    emotions = []
    emotion_keywords = {
        'happy': ['happy', 'joy', 'smile', 'laugh', 'excited'],
        'sad': ['sad', 'miss', 'lonely', 'depressed'],
        'nostalgic': ['remember', 'used to', 'back then', 'old times'],
        'grateful': ['thankful', 'grateful', 'blessed', 'appreciate']
    }
    
    summary_lower = summary.lower()
    for emotion, keywords in emotion_keywords.items():
        if any(keyword in summary_lower for keyword in keywords):
            emotions.append(emotion)
    
    return emotions


def analyze_sentiment(summary):
    """Analyze sentiment of the conversation"""
    # Simplified sentiment analysis
    # In production, use libraries like TextBlob or VADER
    positive_words = ['happy', 'good', 'wonderful', 'great', 'love', 'enjoy']
    negative_words = ['sad', 'bad', 'terrible', 'hate', 'worried', 'concerned']
    
    summary_lower = summary.lower()
    positive_count = sum(1 for word in positive_words if word in summary_lower)
    negative_count = sum(1 for word in negative_words if word in summary_lower)
    
    if positive_count > negative_count:
        return 0.5
    elif negative_count > positive_count:
        return -0.5
    else:
        return 0.0


def assess_engagement(summary):
    """Assess engagement level based on conversation summary"""
    # Simplified engagement assessment
    if len(summary) > 200 and 'story' in summary.lower():
        return 'high'
    elif len(summary) > 100:
        return 'medium'
    else:
        return 'low'


def generate_follow_up_topics(summary):
    """Generate follow-up topics for future calls"""
    topics = []
    if 'family' in summary.lower():
        topics.append('family updates')
    if 'health' in summary.lower():
        topics.append('health check-in')
    if 'hobby' in summary.lower():
        topics.append('hobby activities')
    return topics


def generate_follow_up_questions(summary):
    """Generate follow-up questions for future calls"""
    questions = []
    if 'garden' in summary.lower():
        questions.append("How is your garden doing?")
    if 'family' in summary.lower():
        questions.append("How are your family members doing?")
    return questions


def extract_and_store_memories(senior, conversation, summary):
    """Extract and store important memories from the conversation"""
    try:
        # Always create at least one memory from the conversation
        # This ensures we capture important information even from brief conversations
        
        # Determine memory type based on content
        memory_type = 'fact'  # Default
        title = 'Conversation Summary'
        importance_score = 0.5
        
        summary_lower = summary.lower()
        
        # Check for different types of memories
        if any(keyword in summary_lower for keyword in ['remember when', 'used to', 'when i was', 'back then']):
            memory_type = 'story'
            title = 'Personal Story Shared'
            importance_score = 0.8
        elif any(keyword in summary_lower for keyword in ['like', 'love', 'enjoy', 'favorite', 'prefer']):
            memory_type = 'preference'
            title = 'Personal Preference'
            importance_score = 0.7
        elif any(keyword in summary_lower for keyword in ['family', 'son', 'daughter', 'husband', 'wife', 'friend']):
            memory_type = 'relationship'
            title = 'Family/Friend Mention'
            importance_score = 0.8
        elif any(keyword in summary_lower for keyword in ['health', 'doctor', 'medicine', 'pain', 'feeling']):
            memory_type = 'fact'
            title = 'Health Update'
            importance_score = 0.9
        elif any(keyword in summary_lower for keyword in ['work', 'job', 'career', 'achievement', 'accomplished']):
            memory_type = 'achievement'
            title = 'Work/Career Mention'
            importance_score = 0.7
        
        # Create the memory
        Memory.objects.create(
            senior=senior,
            conversation=conversation,
            memory_type=memory_type,
            title=title,
            content=summary[:500],  # Truncate for storage
            importance_score=importance_score,
            tags=[memory_type, 'conversation']
        )
        
        logger.info(f"Created {memory_type} memory for {senior.name}: {title}")
        
    except Exception as e:
        logger.error(f"Error creating memory for {senior.name}: {str(e)}")


def generate_insights(senior, conversation):
    """Generate insights from the conversation"""
    try:
        # Always generate at least one insight from every conversation
        # This ensures we capture patterns and trends even from brief conversations
        
        summary_lower = conversation.summary.lower()
        
        # Generate insights based on conversation content and analysis
        insights_created = 0
        
        # 1. Mood/Emotional Insight
        if conversation.sentiment_score is not None:
            if conversation.sentiment_score > 0.3:
                ConversationInsight.objects.create(
                    senior=senior,
                    insight_type='mood_trend',
                    title='Positive Mood Detected',
                    description=f'Senior showed positive sentiment (score: {conversation.sentiment_score:.2f}) in recent conversation',
                    data={'sentiment_score': conversation.sentiment_score, 'conversation_id': conversation.id},
                    confidence_score=0.7
                )
                insights_created += 1
            elif conversation.sentiment_score < -0.3:
                ConversationInsight.objects.create(
                    senior=senior,
                    insight_type='mood_trend',
                    title='Concern About Mood',
                    description=f'Senior showed negative sentiment (score: {conversation.sentiment_score:.2f}) in recent conversation',
                    data={'sentiment_score': conversation.sentiment_score, 'conversation_id': conversation.id},
                    confidence_score=0.8
                )
                insights_created += 1
        
        # 2. Health Concern Insight
        if any(keyword in summary_lower for keyword in ['health', 'doctor', 'medicine', 'pain', 'sick', 'not feeling well', 'illness']):
            ConversationInsight.objects.create(
                senior=senior,
                insight_type='health_concern',
                title='Health Discussion',
                description=f'Health-related topics were discussed in recent conversation',
                data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
                confidence_score=0.9
            )
            insights_created += 1
        
        # 3. Social Pattern Insight
        if any(keyword in summary_lower for keyword in ['family', 'son', 'daughter', 'husband', 'wife', 'friend', 'visit', 'call']):
            ConversationInsight.objects.create(
                senior=senior,
                insight_type='social_pattern',
                title='Social Connections Discussed',
                description=f'Family or social connections were mentioned in recent conversation',
                data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
                confidence_score=0.7
            )
            insights_created += 1
        
        # 4. Communication Style Insight
        if conversation.engagement_level:
            ConversationInsight.objects.create(
                senior=senior,
                insight_type='communication_style',
                title=f'{conversation.engagement_level.title()} Engagement Level',
                description=f'Senior showed {conversation.engagement_level} engagement during recent conversation',
                data={'engagement_level': conversation.engagement_level, 'conversation_id': conversation.id},
                confidence_score=0.6
            )
            insights_created += 1
        
        # 5. Interest Change Insight (if topics are discussed)
        if conversation.topics_discussed and len(conversation.topics_discussed) > 0:
            ConversationInsight.objects.create(
                senior=senior,
                insight_type='interest_change',
                title='Topics of Interest',
                description=f'Recent conversation focused on: {", ".join(conversation.topics_discussed)}',
                data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
                confidence_score=0.6
            )
            insights_created += 1
        
        # 6. Default insight if no specific patterns detected
        if insights_created == 0:
            ConversationInsight.objects.create(
                senior=senior,
                insight_type='communication_style',
                title='Conversation Recorded',
                description=f'Conversation with {senior.name} was recorded and analyzed',
                data={'conversation_id': conversation.id, 'summary_length': len(conversation.summary)},
                confidence_score=0.5
            )
            insights_created += 1
        
        logger.info(f"Generated {insights_created} insights for {senior.name}")
        
    except Exception as e:
        logger.error(f"Error generating insights for {senior.name}: {str(e)}")

# from celery import shared_task
# from django.conf import settings
# from django.utils import timezone
# from twilio.rest import Client
# import requests
# import json
# import logging

# from seniors.models import Senior
# from calls.models import Call, CallLog, CallPrompt
# from conversations.models import Conversation, Memory, ConversationInsight

# logger = logging.getLogger(__name__)

# DEFAULT_TIMEOUT = 10  # Seconds

# @shared_task(bind=True)
# def initiate_call_task(self, senior_id, call_purpose='general_checkin', user_id=None, prompt_id=None, assistant_id=None):
#     """
#     Initiate a call to a senior using Vapi.ai (which handles Twilio internally)
    
#     Args:
#         senior_id: ID of the senior to call
#         call_purpose: Purpose of the call
#         user_id: ID of the user initiating the call
#         prompt_id: ID of the CallPrompt to use (optional)
#         assistant_id: ID of the VapiAssistant to use (required)
#     """
#     try:
#         # Get the senior
#         senior = Senior.objects.get(id=senior_id)
        
#         # Validate and get the assistant
#         from calls.models import VapiAssistant
#         if not assistant_id:
#             logger.error("assistant_id is required for initiating calls")
#             return {'status': 'error', 'message': 'assistant_id is required'}
        
#         try:
#             assistant = VapiAssistant.objects.get(id=assistant_id, is_active=True)
#         except VapiAssistant.DoesNotExist:
#             logger.error(f"Assistant with ID {assistant_id} not found or inactive")
#             return {'status': 'error', 'message': 'Assistant not found or inactive'}
        
#         # Get the prompt if provided
#         prompt = None
#         if prompt_id:
#             try:
#                 prompt = CallPrompt.objects.get(id=prompt_id, is_active=True)
#             except CallPrompt.DoesNotExist:
#                 logger.warning(f"Prompt with ID {prompt_id} not found or inactive, using default prompt")
        
#         # If no prompt provided, try to get the default prompt
#         if not prompt:
#             try:
#                 prompt = CallPrompt.objects.get(is_default=True, is_active=True)
#                 logger.info(f"Using default prompt: {prompt.name}")
#             except CallPrompt.DoesNotExist:
#                 logger.info("No default prompt set, using system default")
#                 prompt = None
        
#         # Check monthly usage and limit
#         available_minutes = senior.get_available_minutes()
#         remaining_seconds = max(0, available_minutes * 60)
        
#         if remaining_seconds <= 0:
#             logger.warning(f"Senior {senior.name} has reached their monthly limit.")
#             return {
#                 'status': 'error', 
#                 'message': 'Monthly call limit reached.'
#             }

#         # Create call record with assistant
#         call = Call.objects.create(
#             senior=senior,
#             call_purpose=call_purpose,
#             initiated_by_id=user_id,
#             prompt=prompt,
#             assistant=assistant,
#             twilio_status='initiated'
#         )
        
#         # Log call initiation
#         CallLog.objects.create(
#             call=call,
#             event_type='initiated',
#             message=f'Call initiated to {senior.name} with assistant {assistant.name}',
#             data={'purpose': call_purpose, 'prompt_id': prompt_id, 'assistant_id': assistant_id}
#         )
        
#         # Generate AI prompt with memory context
#         ai_prompt = generate_ai_prompt(senior, call_purpose, prompt)
#         call.ai_prompt = ai_prompt
#         call.save()
        
#         # Log prompt generation
#         CallLog.objects.create(
#             call=call,
#             event_type='prompt_generated',
#             message=f'AI prompt generated for {senior.name}',
#             data={'prompt_length': len(ai_prompt), 'call_purpose': call_purpose}
#         )
        
#         logger.info(f"Generated AI prompt for {senior.name}: {len(ai_prompt)} characters")
        
#         # Start Vapi call directly (Vapi handles Twilio internally)
#         vapi_call_id = start_vapi_call(call, senior)
#         if vapi_call_id:
#             call.vapi_call_id = vapi_call_id
#             call.vapi_status = 'started'
#             call.twilio_status = 'ringing'  # Update status since Vapi is handling the call
#             call.save()
            
#             # Log Vapi call start
#             CallLog.objects.create(
#                 call=call,
#                 event_type='vapi_started',
#                 message=f'Vapi call started with ID: {vapi_call_id}',
#                 data={'vapi_call_id': vapi_call_id, 'remaining_seconds': remaining_seconds}
#             )
            
#             # Schedule enforcement task (30 seconds before limit)
#             if remaining_seconds > 35:
#                 enforce_call_limit_task.apply_async(
#                     args=[call.id],
#                     countdown=int(remaining_seconds - 30)
#                 )
#                 logger.info(f"Scheduled limit enforcement for call {call.id} in {int(remaining_seconds - 30)}s")
#             else:
#                 enforce_call_limit_task.apply_async(args=[call.id], countdown=5)
#                 logger.info(f"Scheduled immediate limit enforcement for call {call.id}")

#             logger.info(f"Call initiated successfully for senior {senior.name}")
#             return {
#                 'status': 'success',
#                 'call_id': call.id,
#                 'vapi_call_id': vapi_call_id
#             }
#         else:
#             # Update call status to failed
#             call.twilio_status = 'failed'
#             call.save()
            
#             CallLog.objects.create(
#                 call=call,
#                 event_type='vapi_failed',
#                 message='Failed to start Vapi call',
#                 data={'error': 'Vapi call creation failed'}
#             )
            
#             return {
#                 'status': 'error',
#                 'message': 'Failed to start Vapi call'
#             }
        
#     except Senior.DoesNotExist:
#         logger.error(f"Senior with ID {senior_id} not found")
#         return {'status': 'error', 'message': 'Senior not found'}
#     except Exception as e:
#         logger.error(f"Error initiating call: {str(e)}")
#         return {'status': 'error', 'message': str(e)}


# def generate_ai_prompt(senior, call_purpose, prompt=None):
#     """
#     Generate an AI prompt for the call with memory context
#     If a CallPrompt is provided, use its template; otherwise use the default prompt
    
#     Args:
#         senior: Senior instance
#         call_purpose: Purpose of the call
#         prompt: CallPrompt instance (optional)
#     """
#     # Get recent conversation context
#     recent_conversations = Conversation.objects.filter(
#         call__senior=senior,
#         call__twilio_status='completed'
#     ).order_by('-created_at')[:3]
    
#     conversation_context = ""
#     if recent_conversations.exists():
#         # Get the most recent conversation for detailed context
#         last_conversation = recent_conversations[0]
        
#         # Build comprehensive context from last conversation
#         context_parts = []
        
#         if last_conversation.summary:
#             context_parts.append(f"Zusammenfassung des letzten Gesprächs: {last_conversation.summary}")
        
#         if last_conversation.topics_discussed:
#             context_parts.append(f"Besprochene Themen: {', '.join(last_conversation.topics_discussed)}")
        
#         if last_conversation.emotions_detected:
#             context_parts.append(f"Beobachtete Emotionen: {', '.join(last_conversation.emotions_detected)}")
        
#         if last_conversation.follow_up_topics:
#             context_parts.append(f"Themen zum Nachfragen: {', '.join(last_conversation.follow_up_topics)}")
        
#         if last_conversation.follow_up_questions:
#             context_parts.append(f"Fragen zum Stellen: {', '.join(last_conversation.follow_up_questions[:3])}")
        
#         conversation_context = "\\n".join(context_parts)
        
#         # Add instructions for using this context
#         conversation_context += "\\n\\nWICHTIG: Verwenden Sie diesen Kontext aus vorherigen Gesprächen natürlich und einfühlsam. Sie können:"
#         conversation_context += "\\n- Auf Themen eingehen, die sie zuvor erwähnt haben, wenn es natürlich passt"
#         conversation_context += "\\n- Nachfragen zu Dingen stellen, über die sie gesprochen haben"
#         conversation_context += "\\n- Sich nach Sorgen oder Interessen erkundigen, die sie hatten"
#         conversation_context += "\\n- Ihr vorheriges Gespräch warm anerkennen, wenn sie sich daran erinnern"
#         conversation_context += "\\n\\nDenken Sie daran: Erzwingen Sie keine Verbindungen - erwähnen Sie vorherige Gespräche nur, wenn es relevant und natürlich zum aktuellen Gespräch passt."
#     else:
#         conversation_context = "Dies ist Ihr erstes Gespräch mit diesem Senioren. Konzentrieren Sie sich darauf, warm und einladend zu sein und etwas über ihre Interessen und Vorlieben zu erfahren."
    
#     # Get current date and time in German format
#     from datetime import datetime
#     current_date = timezone.now()
    
#     # Get day, month, year directly to avoid locale issues
#     day = current_date.day
#     month_num = current_date.month
#     year = current_date.year
#     hour = current_date.hour
#     minute = current_date.minute
    
#     # German month names (using month number to avoid any locale issues)
#     months_de = {
#         1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
#         5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
#         9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
#     }
    
#     # German weekday names
#     weekdays_de = {
#         0: 'Montag', 1: 'Dienstag', 2: 'Mittwoch', 3: 'Donnerstag',
#         4: 'Freitag', 5: 'Samstag', 6: 'Sonntag'
#     }
    
#     # Build date string directly using month number (more reliable than strftime)
#     month_name_de = months_de.get(month_num, f'Month {month_num}')
#     date_str = f"{day}. {month_name_de} {year}"  # e.g., "26. November 2024"
#     time_str = f"{hour:02d}:{minute:02d}"  # e.g., "14:30"
    
#     # Get weekday (0=Monday, 6=Sunday)
#     weekday_num = current_date.weekday()
#     weekday_de = weekdays_de.get(weekday_num, 'Unbekannt')
    
#     # Log the date for debugging
#     logger.info(f"Current date set for AI: {weekday_de}, {date_str}, {time_str} Uhr (Month: {month_num}, Day: {day}, Year: {year})")
    
#     # Prepare template variables
#     template_vars = {
#         'senior_name': senior.name,
#         'senior_age': str(senior.age) if senior.age else 'Nicht angegeben',
#         'interests': ', '.join(senior.interests) if senior.interests else 'Allgemeines Gespräch',
#         'health_notes': senior.health_notes if senior.health_notes else 'Keine',
#         'call_purpose': call_purpose,
#         'conversation_context': conversation_context,
#         'current_date': date_str,
#         'current_time': time_str,
#         'current_weekday': weekday_de,
#         'current_year': str(current_date.year)
#     }
    
#     # If a custom prompt template is provided, use it
#     if prompt and prompt.prompt_template:
#         try:
#             return prompt.prompt_template.format(**template_vars)
#         except KeyError as e:
#             logger.warning(f"Error formatting prompt template: missing placeholder {e}. Using default prompt.")
#         except Exception as e:
#             logger.error(f"Error formatting prompt template: {str(e)}. Using default prompt.")
    
#     # Default prompt template
#     base_prompt = f"""⚠️ KRITISCH WICHTIG - AKTUELLES DATUM ⚠️
# HEUTE IST: {template_vars['current_weekday']}, der {template_vars['current_date']}, {template_vars['current_time']} Uhr.
# Das aktuelle Jahr ist {template_vars['current_year']}.
# VERWENDEN SIE DIESES DATUM IMMER - NIEMALS OKTOBER ODER EIN ANDERES DATUM!

# Sie sind ein freundlicher, geduldiger und einfühlsamer Gesprächspartner für Menschen mit früher oder fortgeschrittener Demenz. Sie müssen IMMER auf Deutsch sprechen. Sprechen Sie langsam, deutlich und verwenden Sie einfache, kurze Sätze. Wiederholen Sie wichtige Informationen sanft, wenn sie vergessen oder missverstanden wurden. Ihre Rolle ist es, ein Gefühl von Sicherheit, Orientierung und emotionaler Wärme zu vermitteln.

# Sie können sich an einfachen Gesprächen über alltägliche Themen beteiligen (z.B. Familie, Essen, Wetter, Musik, Erinnerungen, Hobbys). Reagieren Sie immer positiv - niemals korrigierend oder belehrend. Wenn die Person verwirrt wirkt, führen Sie das Gespräch sanft zu vertrauten und angenehmen Themen. Wenn sie nach einem geliebten Menschen fragen, beruhigen Sie sie ruhig (z.B. "Ich denke, sie kommen bald zurück."). Vermeiden Sie medizinische oder negative Aussagen. Sprechen Sie in ruhigem, freundlichem Ton, wie ein vertrauenswürdiger Freund oder Betreuer. Lassen Sie Pausen im Gespräch, damit die Person Zeit hat zu antworten. Zeigen Sie Verständnis und Mitgefühl durch Ihre Worte ("Das kann ich wirklich verstehen", "Das klingt schön", "Das ist eine schöne Erinnerung").

# Ziel: Das Gespräch sollte die Person emotional stabilisieren, trösten und sanft einbeziehen - wie ein guter Freund, der einfach da ist und zuhört.

# Informationen zum Senioren:
# - Name: {template_vars['senior_name']}
# - Alter: {template_vars['senior_age']}
# - Interessen: {template_vars['interests']}
# - Gesundheitshinweise: {template_vars['health_notes']}

# Gesprächszweck: {template_vars['call_purpose']}

# Richtlinien für demenzfreundliche Kommunikation:
# 1. Sprechen Sie langsam und deutlich
# 2. Verwenden Sie einfache, kurze Sätze
# 3. Wiederholen Sie wichtige Informationen sanft, wenn sie vergessen wurden
# 4. Reagieren Sie immer positiv - niemals korrigieren oder belehren
# 5. Führen Sie verwirrte Gespräche zu vertrauten, angenehmen Themen
# 6. Beruhigen Sie ruhig über geliebte Menschen ("Ich denke, sie kommen bald zurück")
# 7. Vermeiden Sie medizinische oder negative Aussagen
# 8. Sprechen Sie wie ein vertrauenswürdiger Freund oder Betreuer
# 9. Lassen Sie Pausen für Antworten
# 10. Zeigen Sie Verständnis und Mitgefühl
# 11. Konzentrieren Sie sich auf emotionale Stabilität und Komfort
# 12. Seien Sie wie ein guter Freund, der einfach zuhört

# Kontext aus vorherigen Gesprächen:
# {template_vars['conversation_context']}

# WICHTIG: Sie müssen während des gesamten Gesprächs auf Deutsch kommunizieren. Alle Ihre Antworten müssen auf Deutsch sein.

# AKTUELLES DATUM UND ZEIT:
# - Heute ist: {template_vars['current_weekday']}, der {template_vars['current_date']}
# - Aktuelle Uhrzeit: {template_vars['current_time']} Uhr
# - Aktuelles Jahr: {template_vars['current_year']}

# KRITISCH WICHTIG - DATUM UND ZEIT:
# Sie MÜSSEN das oben angegebene Datum verwenden. Es ist das EINZIGE korrekte Datum für heute.

# - Das AKTUELLE Datum ist: {template_vars['current_weekday']}, der {template_vars['current_date']}
# - Die AKTUELLE Zeit ist: {template_vars['current_time']} Uhr
# - Das AKTUELLE Jahr ist: {template_vars['current_year']}

# VERBOTEN - Verwenden Sie NIEMALS diese falschen Daten:
# - NICHT Oktober (Oktober ist der 10. Monat, heute ist {template_vars['current_date']} - das ist NICHT Oktober!)
# - NICHT Oktober 26 oder Oktober 2024 oder Oktober 2025
# - NICHT April 2023
# - NICHT 2023 als Jahr
# - NICHT 2024 als Jahr (das aktuelle Jahr ist {template_vars['current_year']})
# - NICHT irgendein anderes Datum außer dem oben angegebenen

# WICHTIG: Der aktuelle Monat in {template_vars['current_date']} ist der korrekte Monat. Oktober (Monat 10) ist FALSCH!

# Wenn Sie nach dem Datum gefragt werden, antworten Sie EXAKT so:
# "Heute ist {template_vars['current_weekday']}, der {template_vars['current_date']}, {template_vars['current_time']} Uhr."

# Das Datum {template_vars['current_date']} ist das EINZIGE korrekte Datum für heute. Verwenden Sie es IMMER.

# Denken Sie daran: Dieses Gespräch wird zusammengefasst und gespeichert, um zukünftige Anrufe zu personalisieren. Konzentrieren Sie sich darauf, emotionalen Komfort, Sicherheit und sanfte Einbeziehung zu schaffen - bewahren Sie ihre Geschichten und bieten Sie emotionale Unterstützung."""

#     return base_prompt


# def generate_personalized_greeting(senior, ai_prompt):
#     """
#     Generate a personalized greeting that references previous conversations if available.
#     Uses senior's custom first_message if set, otherwise generates default greeting.
#     Supports template variables: {senior.name} and {companion.name}
#     """
#     # Prepare template variables
#     companion_name = senior.companion_name if senior.companion_name else "Ihr KI-Begleiter"
#     template_vars = {
#         'senior.name': senior.name,
#         'companion.name': companion_name
#     }
    
#     # If senior has a custom first_message, use it with template variable replacement
#     if senior.first_message:
#         try:
#             # Replace template variables
#             greeting = senior.first_message
#             for key, value in template_vars.items():
#                 greeting = greeting.replace(f"{{{key}}}", value)
#             return greeting
#         except Exception as e:
#             logger.warning(f"Error formatting custom first_message for {senior.name}: {str(e)}. Using default greeting.")
    
#     # Default behavior: Check for previous conversation context in the prompt
#     if "Zusammenfassung des letzten Gesprächs:" in ai_prompt:
#         # Extract context about last conversation
#         if "gesundheit" in ai_prompt.lower() or "gefühl" in ai_prompt.lower():
#             return f"Hallo {senior.name}! Ich rufe an, um nachzufragen, wie es Ihnen heute geht. Ich hoffe, alles geht gut für Sie. Hier ist {companion_name}."
#         else:
#             return f"Hallo {senior.name}! Es ist schön, wieder mit Ihnen zu sprechen. Ich wollte nachfragen, wie es Ihnen geht. Hier ist {companion_name}."
#     else:
#         # First conversation
#         return f"Hallo {senior.name}! Hier ist {companion_name}. Ich rufe an, um nachzufragen und ein schönes Gespräch mit Ihnen zu führen."


# def create_vapi_assistant_with_prompt(senior, ai_prompt, base_assistant_id=None):
#     """
#     Create a Vapi.ai assistant with custom system prompt for this senior
#     Uses the configured voice settings from the provided base_assistant_id
    
#     Args:
#         senior: Senior instance
#         ai_prompt: AI prompt text
#         base_assistant_id: Base VAPI assistant ID to copy voice settings from
#     """
#     try:
#         headers = {
#             'Authorization': f'Bearer {settings.VAPI_API_KEY}',
#             'Content-Type': 'application/json'
#         }
        
#         # Default voice configuration - German voice
#         voice_config = {
#             "provider": "azure",
#             "voiceId": "de-DE-KatjaNeural"  # German female voice
#         }
        
#         # Try to get voice configuration from base assistant
#         if base_assistant_id and base_assistant_id.strip():
#             try:
#                 response = requests.get(
#                     f'https://api.vapi.ai/assistant/{base_assistant_id}',
#                     headers=headers
#                 )
#                 if response.status_code == 200:
#                     existing_assistant = response.json()
#                     voice_config = existing_assistant.get('voice', voice_config)
#                     logger.info(f"Using voice config from base assistant {base_assistant_id}: {voice_config}")
#             except Exception as e:
#                 logger.warning(f"Could not fetch base assistant voice config: {str(e)}")
        
#         # Log the date that will be in the prompt for verification
#         prompt_date_check = ai_prompt[:500] if len(ai_prompt) > 500 else ai_prompt
#         # Extract date from prompt for logging
#         import re
#         date_match = re.search(r'der (\d+\. \w+ \d{4})', ai_prompt)
#         date_in_prompt = date_match.group(1) if date_match else "NOT FOUND"
#         logger.info(f"Creating Vapi assistant for {senior.name} - Date in prompt: {date_in_prompt}")
#         logger.info(f"Prompt preview (first 500 chars): {prompt_date_check}")
        
#         # Get current date directly (same calculation as in generate_ai_prompt)
#         from datetime import datetime
#         current_date = timezone.now()
#         day = current_date.day
#         month_num = current_date.month
#         year = current_date.year
        
#         months_de = {
#             1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
#             5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
#             9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
#         }
#         weekdays_de = {
#             0: 'Montag', 1: 'Dienstag', 2: 'Mittwoch', 3: 'Donnerstag',
#             4: 'Freitag', 5: 'Samstag', 6: 'Sonntag'
#         }
        
#         month_name_de = months_de.get(month_num, 'Unbekannt')
#         weekday_de = weekdays_de.get(current_date.weekday(), 'Unbekannt')
#         current_date_str = f"{day}. {month_name_de} {year}"
        
#         # Add an even more explicit date instruction at the very beginning
#         enhanced_prompt = f"""SYSTEM INFORMATION - MANDATORY:
# CURRENT DATE: {current_date_str}
# CURRENT YEAR: {year}
# CURRENT MONTH: {month_name_de} (Month {month_num})
# CURRENT DAY: {day}
# CURRENT WEEKDAY: {weekday_de}

# YOU MUST ALWAYS USE THIS DATE: {current_date_str}
# NEVER SAY OCTOBER - OCTOBER IS MONTH 10, TODAY IS MONTH {month_num} ({month_name_de})
# IF ASKED FOR DATE, RESPOND EXACTLY: "Heute ist {weekday_de}, der {current_date_str}"

# {ai_prompt.strip()}"""
        
#         assistant_config = {
#             "name": f"Senior Companion - {senior.name}",
#             "model": {
#                 "provider": "openai",
#                 "model": "gpt-4o-mini",
#                 "messages": [
#                     {
#                         "role": "system",
#                         "content": enhanced_prompt
#                     }
#                 ],
#                 "temperature": 0.7
#             },
#             "voice": voice_config,
#             "firstMessage": generate_personalized_greeting(senior, ai_prompt)
#         }
        
#         logger.info(f"Creating personalized Vapi assistant for {senior.name} with voice: {voice_config}")
        
#         response = requests.post(
#             'https://api.vapi.ai/assistant',
#             headers=headers,
#             json=assistant_config
#         )
        
#         if response.status_code == 201:
#             assistant = response.json()
#             logger.info(f"Personalized Vapi assistant created successfully for {senior.name}: {assistant['id']}")
#             return assistant['id']
#         else:
#             logger.error(f"Failed to create Vapi assistant: {response.status_code} - {response.text}")
#             return None
            
#     except Exception as e:
#         logger.error(f"Error creating Vapi assistant: {str(e)}")
#         return None


# def start_vapi_call(call, senior):
#     """
#     Start a Vapi.ai call with the generated prompt using the selected assistant
#     """
#     try:
#         headers = {
#             'Authorization': f'Bearer {settings.VAPI_API_KEY}',
#             'Content-Type': 'application/json'
#         }
        
#         # Get the base assistant ID from the call's assistant field
#         if not call.assistant:
#             logger.error(f"No assistant assigned to call {call.id}")
#             return None
        
#         base_assistant_id = call.assistant.assistant_id
#         logger.info(f"Using base assistant {call.assistant.name} (ID: {base_assistant_id}) for {senior.name}")
        
#         # Always create personalized assistants with custom prompts
#         # This ensures each senior gets personalized knowledge and greetings
#         logger.info(f"Creating personalized assistant for {senior.name} with custom prompt")
#         assistant_id = create_vapi_assistant_with_prompt(senior, call.ai_prompt, base_assistant_id)
        
#         if not assistant_id:
#             logger.error(f"Failed to create personalized assistant for {senior.name}")
#             return None
        
#         # Store the assistant ID for this call
#         call.vapi_assistant_id = assistant_id
#         call.save()
        
#         # Now make the call with the assistant
#         data = {
#             'assistantId': assistant_id,
#             'phoneNumberId': settings.VAPI_PHONE_NUMBER_ID,
#             'customer': {
#                 'number': senior.phone_number
#             }
#         }
        
#         logger.info(f"Starting Vapi call for {senior.name} with assistant: {assistant_id}")
        
#         response = requests.post(
#             'https://api.vapi.ai/call',
#             headers=headers,
#             json=data,
#             # timeout=DEFAULT_TIMEOUT
#         )
        
#         if response.status_code in [200, 201]:
#             result = response.json()
#             logger.info(f"Vapi call created successfully: {result}")
#             logger.info(f"Using assistant: {assistant_id}")
#             return result.get('id')
#         else:
#             logger.error(f"Vapi call failed: {response.text}")
#             return None
            
#     except Exception as e:
#         logger.error(f"Error starting Vapi call: {str(e)}")
#         return None


# @shared_task(bind=True)
# def check_and_update_call_statuses(self):
#     """
#     Check Vapi.ai for call statuses and update our database
#     """
#     try:
#         headers = {
#             'Authorization': f'Bearer {settings.VAPI_API_KEY}',
#             'Content-Type': 'application/json'
#         }
        
#         # Get all calls that are not completed (including failed starts)
#         active_calls = Call.objects.filter(
#             twilio_status__in=['initiated', 'ringing', 'in-progress']
#         )
        
#         # Only check completed calls that don't have a conversation record yet
#         completed_calls = Call.objects.filter(
#             twilio_status='completed',
#             vapi_call_id__isnull=False,
#             conversation__isnull=True
#         )
        
#         updated_count = 0
        
#         # Process active calls
#         for call in active_calls:
#             try:
#                 # Handle calls without Vapi Call ID (failed to start)
#                 if not call.vapi_call_id:
#                     # Check if call is older than 5 minutes and still initiated
#                     from datetime import timedelta
                    
#                     if call.twilio_status == 'initiated' and call.created_at < timezone.now() - timedelta(minutes=5):
#                         logger.info(f"Marking call {call.id} as failed (no Vapi Call ID after 5 minutes)")
#                         call.twilio_status = 'failed'
#                         call.vapi_status = 'failed'
#                         call.save()
#                         updated_count += 1
                        
#                         # Log the status change
#                         CallLog.objects.create(
#                             call=call,
#                             event_type='status_changed',
#                             message=f'Status updated to failed (no Vapi Call ID after timeout)',
#                             data={'old_status': 'initiated', 'new_status': 'failed', 'reason': 'timeout'}
#                         )
#                     continue
                
#                 # Handle calls with Vapi Call ID
#                 response = requests.get(
#                     f'https://api.vapi.ai/call/{call.vapi_call_id}',
#                     headers=headers
#                 )
                
#                 if response.status_code == 200:
#                     call_data = response.json()
#                     vapi_status_raw = call_data.get('status', 'unknown')
#                     # Normalize Vapi status to consistent kebab-case lowercase
#                     vapi_status = str(vapi_status_raw).strip()
#                     vapi_status = vapi_status.replace('_', '-').lower()
#                     if vapi_status == 'inprogress':
#                         vapi_status = 'in-progress'
                    
#                     # Map Vapi status to our status
#                     status_mapping = {
#                         'queued': 'initiated',
#                         'ringing': 'ringing', 
#                         'in-progress': 'in-progress',
#                         'completed': 'completed',
#                         'ended': 'completed',
#                         'busy': 'busy',
#                         'no-answer': 'no_answer',
#                         'failed': 'failed',
#                         'canceled': 'cancelled',
#                         'answered': 'in-progress',
#                         'started': 'in-progress',
#                         'connected': 'in-progress'
#                     }
                    
#                     if vapi_status in status_mapping:
#                         new_status = status_mapping[vapi_status]
#                         old_status = call.twilio_status

#                         # Ensure start time is set once the call is in progress
#                         start_time_set = False
#                         if new_status == 'in-progress' and not call.call_start_time:
#                             call.call_start_time = timezone.now()
#                             start_time_set = True
                        
#                         if old_status != new_status:
#                             logger.info(f"Updating call {call.id} status from {old_status} to {new_status}")
#                             call.twilio_status = new_status
#                             call.vapi_status = vapi_status
                            
#                             # If call transitions to in-progress and start time not set, set it
#                             if new_status == 'in-progress' and not call.call_start_time:
#                                 call.call_start_time = timezone.now()
                            
#                             # Update duration and check for errors if ended
#                             duration = call_data.get('duration') or call_data.get('durationSeconds') or call_data.get('duration_ms')
#                             ended_reason = call_data.get('endedReason')
#                             ended_message = call_data.get('endedMessage')
                            
#                             if isinstance(duration, (int, float)) and duration and duration > 1000:
#                                 # convert ms to seconds if it looks like ms
#                                 duration = int(duration / 1000)
#                             if duration:
#                                 call.duration = duration
                            
#                             # Log details for failed/ended calls
#                             log_message = f'Status updated from {old_status} to {new_status} via Vapi API check'
#                             if new_status in ['failed', 'completed'] and ended_reason:
#                                 log_message += f' (Reason: {ended_reason})'
#                                 if ended_message:
#                                     log_message += f': {ended_message}'
                            
#                             # Update timing information for completed calls
#                             if new_status == 'completed' and old_status != 'completed':
#                                 call.call_end_time = timezone.now()
#                                 if call.call_start_time and not call.duration:
#                                     duration_seconds = (call.call_end_time - call.call_start_time).total_seconds()
#                                     call.duration = int(duration_seconds)
                            
#                             # Process conversation data for completed calls (regardless of status change)
#                             if new_status == 'completed':
#                                 # Check if conversation already exists
#                                 if not Conversation.objects.filter(call=call).exists():
#                                     logger.info(f"Processing conversation data for completed call {call.id}")
#                                     try:
#                                         process_completed_call_from_api(call, call_data)
#                                     except Exception as e:
#                                         logger.error(f"Error processing conversation for call {call.id}: {str(e)}")
#                                 else:
#                                     logger.debug(f"Conversation already exists for call {call.id}")
                            
#                             call.save()
#                             updated_count += 1
                            
#                             # Log the status change
#                             CallLog.objects.create(
#                                 call=call,
#                                 event_type='status_changed',
#                                 message=log_message,
#                                 data={'old_status': old_status, 'new_status': new_status, 'vapi_status': vapi_status, 'duration': duration, 'ended_reason': ended_reason}
#                             )
#                         else:
#                             # If status unchanged but we set start time, persist it
#                             if start_time_set:
#                                 call.save()
#                                 updated_count += 1
#                                 CallLog.objects.create(
#                                     call=call,
#                                     event_type='status_changed',
#                                     message='Start time set while status remained in-progress',
#                                     data={'status': new_status}
#                                 )
#                             else:
#                                 logger.debug(f"Call {call.id} status unchanged: {old_status}")
#                     else:
#                         logger.warning(f"Unknown Vapi status for call {call.id}: {vapi_status}")
                
#             except Exception as e:
#                 logger.error(f"Error checking call {call.id}: {str(e)}")
        
#         # Process completed calls for conversation data
#         conversation_processed_count = 0
#         for call in completed_calls:
#             try:
#                 # Check if conversation already exists
#                 if not Conversation.objects.filter(call=call).exists():
#                     logger.info(f"Checking completed call {call.id} for conversation processing")
                    
#                     # Get call data from VAPI API
#                     response = requests.get(
#                         f'https://api.vapi.ai/call/{call.vapi_call_id}',
#                         headers=headers
#                     )
                    
#                     if response.status_code == 200:
#                         call_data = response.json()
#                         vapi_status = call_data.get('status', 'unknown')
                        
#                         if vapi_status in ['completed', 'ended']:
#                             logger.info(f"Processing conversation for completed call {call.id}")
#                             try:
#                                 process_completed_call_from_api(call, call_data)
#                                 conversation_processed_count += 1
#                                 logger.info(f"Successfully processed conversation for call {call.id}")
#                             except Exception as e:
#                                 logger.error(f"Error processing conversation for call {call.id}: {str(e)}")
#                     else:
#                         logger.warning(f"Could not fetch VAPI data for call {call.id}: {response.status_code}")
                        
#             except Exception as e:
#                 logger.error(f"Error processing completed call {call.id}: {str(e)}")
        
#         logger.info(f"Updated {updated_count} call statuses and processed {conversation_processed_count} conversations")
#         return {'status': 'success', 'updated_count': updated_count, 'conversations_processed': conversation_processed_count}
        
#     except Exception as e:
#         logger.error(f"Error in check_and_update_call_statuses: {str(e)}")
#         return {'status': 'error', 'message': str(e)}


# @shared_task(bind=True)
# def process_vapi_webhook_task(self, webhook_data):
#     """
#     Process Vapi.ai webhook data and update conversation records
    
#     Args:
#         webhook_data: Data received from Vapi webhook
#     """
#     try:
#         call_id = webhook_data.get('callId')
#         status_raw = webhook_data.get('status')
#         # Normalize Vapi status from webhook
#         status = str(status_raw).strip() if status_raw is not None else 'unknown'
#         status = status.replace('_', '-').lower()
#         if status == 'inprogress':
#             status = 'in-progress'
        
#         if not call_id:
#             logger.error("No callId in Vapi webhook data")
#             return {'status': 'error', 'message': 'No callId provided'}
        
#         # Find the call by Vapi call ID
#         try:
#             call = Call.objects.get(vapi_call_id=call_id)
#         except Call.DoesNotExist:
#             logger.error(f"Call with Vapi ID {call_id} not found")
#             return {'status': 'error', 'message': 'Call not found'}
        
#         # Update call status
#         call.vapi_status = status
        
#         # Map Vapi status to Twilio status
#         status_mapping = {
#             'queued': 'initiated',
#             'ringing': 'ringing', 
#             'in-progress': 'in-progress',
#             'completed': 'completed',
#             'ended': 'completed',
#             'busy': 'busy',
#             'no-answer': 'no_answer',
#             'failed': 'failed',
#             'canceled': 'cancelled',
#             'answered': 'in-progress',
#             'started': 'in-progress',
#             'connected': 'in-progress'
#         }
        
#         if status in status_mapping:
#             old_status = call.twilio_status
#             new_status = status_mapping[status]
            
#             # Ensure start time is set once the call is in progress
#             start_time_set = False
#             if new_status == 'in-progress' and not call.call_start_time:
#                 call.call_start_time = timezone.now()
#                 start_time_set = True
            
#             if old_status != new_status:
#                 logger.info(f"Webhook: Updating call {call.id} status from {old_status} to {new_status}")
#                 call.twilio_status = new_status
                
#                 # If call transitions to in-progress and start time not set, set it
#                 if new_status == 'in-progress' and not call.call_start_time:
#                     call.call_start_time = timezone.now()
                
#                 # Update timing information for completed calls
#                 if new_status == 'completed' and old_status != 'completed':
#                     call.call_end_time = timezone.now()
#                     # Try to set duration from webhook payload if present
#                     webhook_duration = (
#                         webhook_data.get('duration') or
#                         webhook_data.get('durationSeconds') or
#                         webhook_data.get('duration_ms')
#                     )
#                     if isinstance(webhook_duration, (int, float)) and webhook_duration and webhook_duration > 1000:
#                         webhook_duration = int(webhook_duration / 1000)
#                     if webhook_duration:
#                         call.duration = webhook_duration
#                     if call.call_start_time and not call.duration:
#                         duration_seconds = (call.call_end_time - call.call_start_time).total_seconds()
#                         call.duration = int(duration_seconds)
                
#                 call.save()
                
#                 # Log the status change
#                 CallLog.objects.create(
#                     call=call,
#                     event_type='status_changed',
#                     message=f'Status updated from {old_status} to {new_status} via webhook',
#                     data={'old_status': old_status, 'new_status': new_status, 'vapi_status': status}
#                 )
#             else:
#                 # If status unchanged but we set start time, persist it
#                 if start_time_set:
#                     call.save()
#                     CallLog.objects.create(
#                         call=call,
#                         event_type='status_changed',
#                         message='Start time set while status remained in-progress (webhook)',
#                         data={'status': new_status}
#                     )
#                 else:
#                     logger.debug(f"Webhook: Call {call.id} status unchanged: {old_status}")
#         else:
#             logger.warning(f"Webhook: Unknown Vapi status for call {call.id}: {status}")
        
#         call.save()
        
#         # Process completed calls
#         if status in ['ended', 'completed']:
#             process_completed_call(call, webhook_data)
        
#         # Log webhook event
#         CallLog.objects.create(
#             call=call,
#             event_type='webhook_received',
#             message=f'Vapi webhook received: {status}',
#             data=webhook_data
#         )
        
#         logger.info(f"Vapi webhook processed for call {call.id}")
#         return {'status': 'success', 'call_id': call.id}
        
#     except Exception as e:
#         logger.error(f"Error processing Vapi webhook: {str(e)}")
#         return {'status': 'error', 'message': str(e)}


# @shared_task(bind=True)
# def enforce_call_limit_task(self, call_id):
#     """
#     Enforces the call limit by injecting a goodbye message and hanging up.
#     """
#     import time
#     try:
#         call = Call.objects.get(id=call_id)
#         if call.twilio_status not in ['initiated', 'ringing', 'in-progress'] or call.is_limit_enforced:
#             return {'status': 'skipped'}

#         headers = {'Authorization': f'Bearer {settings.VAPI_API_KEY}', 'Content-Type': 'application/json'}
        
#         # 1. Inject goodbye message
#         message_data = {
#             "message": {
#                 "role": "system",
#                 "content": "TIME LIMIT REACHED: Please say a warm goodbye in 1–2 sentences and end the call. Do not ask any more questions."
#             }
#         }
#         requests.post(f'https://api.vapi.ai/call/{call.vapi_call_id}/message', headers=headers, json=message_data)
        
#         call.is_limit_enforced = True
#         call.save()
        
#         # 2. Hard hangup after 25 seconds
#         # time.sleep(25)
#         call.refresh_from_db()
#         if call.twilio_status in ['initiated', 'ringing', 'in-progress']:
#             requests.post(f'https://api.vapi.ai/call/{call.vapi_call_id}/end', headers=headers)
#             CallLog.objects.create(call=call, event_type='hard_hangup', message='Call ended by system limit')
            
#     except Exception as e:
#         logger.error(f"Error in enforcement: {str(e)}")


# def process_completed_call(call, webhook_data):
#     """
#     Process a completed call and extract conversation data from webhook
#     """
#     try:
#         # Get transcript and summary from webhook data
#         transcript = webhook_data.get('transcript', '')
#         summary = webhook_data.get('summary', '')
        
#         # Create conversation record
#         conversation = Conversation.objects.create(
#             call=call,
#             transcript=transcript,
#             summary=summary,
#             topics_discussed=extract_topics(summary),
#             key_memories=extract_memories(summary),
#             emotions_detected=extract_emotions(summary),
#             sentiment_score=analyze_sentiment(summary),
#             engagement_level=assess_engagement(summary),
#             follow_up_topics=generate_follow_up_topics(summary),
#             follow_up_questions=generate_follow_up_questions(summary)
#         )
        
#         # Extract and store memories
#         extract_and_store_memories(call.senior, conversation, summary)
        
#         # Generate insights
#         generate_insights(call.senior, conversation)
        
#         # Deduct from extra balance if needed
#         # If total duration > monthly_allowance, the overflow should come from extra_minutes_balance
#         senior = call.senior
#         total_used_mins = senior.get_monthly_usage_minutes()
#         if total_used_mins > senior.monthly_timelimit:
#             overflow = total_used_mins - senior.monthly_timelimit
#             # This is naive since it subtracts the WHOLE usage every time, 
#             # but Senior.get_monthly_usage_minutes() returns total for the month.
#             # We only want to deduct the duration of THIS call from the balance 
#             # if we are already in the "extra" zone.
            
#             call_mins = (call.duration or 0) / 60
#             # If the current call pushed it over OR it was already over
#             if total_used_mins - call_mins < senior.monthly_timelimit:
#                 # Part of the call was in allowance, part in extra
#                 deduction = total_used_mins - senior.monthly_timelimit
#             else:
#                 # Entire call was in extra
#                 deduction = call_mins
            
#             if deduction > 0:
#                 senior.extra_minutes_balance = max(0, senior.extra_minutes_balance - deduction)
#                 senior.save()
#                 logger.info(f"Deducted {deduction:.2f} mins from extra balance for {senior.name}")

#         logger.info(f"Completed call processed for senior {call.senior.name}")
        
#     except Exception as e:
#         logger.error(f"Error processing completed call: {str(e)}")


# def process_completed_call_from_api(call, api_data):
#     """
#     Process a completed call and extract conversation data from API response
#     """
#     try:
#         # Check if conversation already exists
#         if Conversation.objects.filter(call=call).exists():
#             logger.info(f"Conversation already exists for call {call.id}, skipping")
#             return
        
#         # Get transcript and summary from API data
#         transcript_text = api_data.get('transcript', '')
#         summary = api_data.get('summary', '')
        
#         # If no summary, create a basic one
#         if not summary and transcript_text:
#             summary = f"Conversation with {call.senior.name} lasting {call.duration or 'unknown'} seconds."
        
#         # Create conversation record
#         conversation = Conversation.objects.create(
#             call=call,
#             transcript=transcript_text,
#             summary=summary,
#             topics_discussed=extract_topics(summary),
#             key_memories=extract_memories(summary),
#             emotions_detected=extract_emotions(summary),
#             sentiment_score=analyze_sentiment(summary),
#             engagement_level=assess_engagement(summary),
#             follow_up_topics=generate_follow_up_topics(summary),
#             follow_up_questions=generate_follow_up_questions(summary)
#         )
        
#         # Extract and store memories
#         extract_and_store_memories(call.senior, conversation, summary)
        
#         # Generate insights
#         generate_insights(call.senior, conversation)
        
#         # Deduct from extra balance if needed
#         senior = call.senior
#         total_used_mins = senior.get_monthly_usage_minutes()
#         if total_used_mins > senior.monthly_timelimit:
#             call_mins = (call.duration or 0) / 60
#             if total_used_mins - call_mins < senior.monthly_timelimit:
#                 deduction = total_used_mins - senior.monthly_timelimit
#             else:
#                 deduction = call_mins
            
#             if deduction > 0:
#                 senior.extra_minutes_balance = max(0, senior.extra_minutes_balance - deduction)
#                 senior.save()
#                 logger.info(f"Deducted {deduction:.2f} mins from extra balance for {senior.name}")

#         logger.info(f"Completed call processed from API for senior {call.senior.name}")
        
#     except Exception as e:
#         logger.error(f"Error processing completed call from API: {str(e)}")


# def extract_topics(summary):
#     """Extract topics from conversation summary"""
#     topics = []
#     topic_keywords = {
#         'family': ['family', 'children', 'kids', 'son', 'daughter', 'grandchildren', 'spouse', 'husband', 'wife'],
#         'work': ['work', 'job', 'career', 'office', 'business', 'retirement'],
#         'hobby': ['hobby', 'hobbies', 'painting', 'coding', 'football', 'sports', 'music', 'reading', 'gardening'],
#         'travel': ['travel', 'vacation', 'trip', 'journey', 'visit'],
#         'health': ['health', 'doctor', 'medicine', 'exercise', 'walking', 'feeling'],
#         'food': ['food', 'cooking', 'meal', 'restaurant', 'recipe'],
#         'weather': ['weather', 'rain', 'sunny', 'cold', 'hot'],
#         'memories': ['remember', 'used to', 'back then', 'old times', 'childhood'],
#         'daily_routine': ['daily', 'routine', 'morning', 'evening', 'schedule'],
#         'social': ['friends', 'neighbors', 'community', 'social', 'visit']
#     }
    
#     summary_lower = summary.lower()
#     for topic, keywords in topic_keywords.items():
#         if any(keyword in summary_lower for keyword in keywords):
#             topics.append(topic)
    
#     return topics


# def extract_memories(summary):
#     """Extract key memories from conversation summary"""
#     memories = []
#     memory_indicators = [
#         'remember when', 'used to', 'back then', 'old times', 'childhood',
#         'when I was young', 'in my day', 'years ago', 'story about',
#         'told me about', 'shared a memory', 'reminisced about'
#     ]
    
#     summary_lower = summary.lower()
#     if any(indicator in summary_lower for indicator in memory_indicators):
#         memories.append("Personal memories shared")
    
#     # Look for specific memory types
#     if 'family' in summary_lower and any(word in summary_lower for word in ['remember', 'used to', 'story']):
#         memories.append("Family memories")
    
#     if 'work' in summary_lower and any(word in summary_lower for word in ['remember', 'used to', 'career']):
#         memories.append("Work/career memories")
    
#     return memories


# def extract_emotions(summary):
#     """Extract emotions from conversation summary"""
#     emotions = []
#     emotion_keywords = {
#         'happy': ['happy', 'joy', 'smile', 'laugh', 'excited'],
#         'sad': ['sad', 'miss', 'lonely', 'depressed'],
#         'nostalgic': ['remember', 'used to', 'back then', 'old times'],
#         'grateful': ['thankful', 'grateful', 'blessed', 'appreciate']
#     }
    
#     summary_lower = summary.lower()
#     for emotion, keywords in emotion_keywords.items():
#         if any(keyword in summary_lower for keyword in keywords):
#             emotions.append(emotion)
    
#     return emotions


# def analyze_sentiment(summary):
#     """Analyze sentiment of the conversation"""
#     # Simplified sentiment analysis
#     # In production, use libraries like TextBlob or VADER
#     positive_words = ['happy', 'good', 'wonderful', 'great', 'love', 'enjoy']
#     negative_words = ['sad', 'bad', 'terrible', 'hate', 'worried', 'concerned']
    
#     summary_lower = summary.lower()
#     positive_count = sum(1 for word in positive_words if word in summary_lower)
#     negative_count = sum(1 for word in negative_words if word in summary_lower)
    
#     if positive_count > negative_count:
#         return 0.5
#     elif negative_count > positive_count:
#         return -0.5
#     else:
#         return 0.0


# def assess_engagement(summary):
#     """Assess engagement level based on conversation summary"""
#     # Simplified engagement assessment
#     if len(summary) > 200 and 'story' in summary.lower():
#         return 'high'
#     elif len(summary) > 100:
#         return 'medium'
#     else:
#         return 'low'


# def generate_follow_up_topics(summary):
#     """Generate follow-up topics for future calls"""
#     topics = []
#     if 'family' in summary.lower():
#         topics.append('family updates')
#     if 'health' in summary.lower():
#         topics.append('health check-in')
#     if 'hobby' in summary.lower():
#         topics.append('hobby activities')
#     return topics


# def generate_follow_up_questions(summary):
#     """Generate follow-up questions for future calls"""
#     questions = []
#     if 'garden' in summary.lower():
#         questions.append("How is your garden doing?")
#     if 'family' in summary.lower():
#         questions.append("How are your family members doing?")
#     return questions


# def extract_and_store_memories(senior, conversation, summary):
#     """Extract and store important memories from the conversation"""
#     try:
#         # Always create at least one memory from the conversation
#         # This ensures we capture important information even from brief conversations
        
#         # Determine memory type based on content
#         memory_type = 'fact'  # Default
#         title = 'Conversation Summary'
#         importance_score = 0.5
        
#         summary_lower = summary.lower()
        
#         # Check for different types of memories
#         if any(keyword in summary_lower for keyword in ['remember when', 'used to', 'when i was', 'back then']):
#             memory_type = 'story'
#             title = 'Personal Story Shared'
#             importance_score = 0.8
#         elif any(keyword in summary_lower for keyword in ['like', 'love', 'enjoy', 'favorite', 'prefer']):
#             memory_type = 'preference'
#             title = 'Personal Preference'
#             importance_score = 0.7
#         elif any(keyword in summary_lower for keyword in ['family', 'son', 'daughter', 'husband', 'wife', 'friend']):
#             memory_type = 'relationship'
#             title = 'Family/Friend Mention'
#             importance_score = 0.8
#         elif any(keyword in summary_lower for keyword in ['health', 'doctor', 'medicine', 'pain', 'feeling']):
#             memory_type = 'fact'
#             title = 'Health Update'
#             importance_score = 0.9
#         elif any(keyword in summary_lower for keyword in ['work', 'job', 'career', 'achievement', 'accomplished']):
#             memory_type = 'achievement'
#             title = 'Work/Career Mention'
#             importance_score = 0.7
        
#         # Create the memory with a unique title
#         from django.utils import timezone
#         timestamp = timezone.now().strftime('%Y-%m-%d')
#         unique_title = f"{title} ({timestamp} #{conversation.id})"

#         Memory.objects.create(
#             senior=senior,
#             conversation=conversation,
#             memory_type=memory_type,
#             title=unique_title,
#             content=summary[:500],  # Truncate for storage
#             importance_score=importance_score,
#             tags=[memory_type, 'conversation']
#         )
        
#         logger.info(f"Created {memory_type} memory for {senior.name}: {unique_title}")
        
#     except Exception as e:
#         logger.error(f"Error creating memory for {senior.name}: {str(e)}")


# def generate_insights(senior, conversation):
#     """Generate insights from the conversation"""
#     try:
#         # Always generate at least one insight from every conversation
#         # This ensures we capture patterns and trends even from brief conversations
        
#         summary_lower = conversation.summary.lower()
        
#         # Generate insights based on conversation content and analysis
#         insights_created = 0
        
#         # 1. Mood/Emotional Insight
#         if conversation.sentiment_score is not None:
#             if conversation.sentiment_score > 0.3:
#                 ConversationInsight.objects.create(
#                     senior=senior,
#                     insight_type='mood_trend',
#                     title='Positive Mood Detected',
#                     description=f'Senior showed positive sentiment (score: {conversation.sentiment_score:.2f}) in recent conversation',
#                     data={'sentiment_score': conversation.sentiment_score, 'conversation_id': conversation.id},
#                     confidence_score=0.7
#                 )
#                 insights_created += 1
#             elif conversation.sentiment_score < -0.3:
#                 ConversationInsight.objects.create(
#                     senior=senior,
#                     insight_type='mood_trend',
#                     title='Concern About Mood',
#                     description=f'Senior showed negative sentiment (score: {conversation.sentiment_score:.2f}) in recent conversation',
#                     data={'sentiment_score': conversation.sentiment_score, 'conversation_id': conversation.id},
#                     confidence_score=0.8
#                 )
#                 insights_created += 1
        
#         # 2. Health Concern Insight
#         if any(keyword in summary_lower for keyword in ['health', 'doctor', 'medicine', 'pain', 'sick', 'not feeling well', 'illness']):
#             ConversationInsight.objects.create(
#                 senior=senior,
#                 insight_type='health_concern',
#                 title='Health Discussion',
#                 description=f'Health-related topics were discussed in recent conversation',
#                 data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
#                 confidence_score=0.9
#             )
#             insights_created += 1
        
#         # 3. Social Pattern Insight
#         if any(keyword in summary_lower for keyword in ['family', 'son', 'daughter', 'husband', 'wife', 'friend', 'visit', 'call']):
#             ConversationInsight.objects.create(
#                 senior=senior,
#                 insight_type='social_pattern',
#                 title='Social Connections Discussed',
#                 description=f'Family or social connections were mentioned in recent conversation',
#                 data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
#                 confidence_score=0.7
#             )
#             insights_created += 1
        
#         # 4. Communication Style Insight
#         if conversation.engagement_level:
#             ConversationInsight.objects.create(
#                 senior=senior,
#                 insight_type='communication_style',
#                 title=f'{conversation.engagement_level.title()} Engagement Level',
#                 description=f'Senior showed {conversation.engagement_level} engagement during recent conversation',
#                 data={'engagement_level': conversation.engagement_level, 'conversation_id': conversation.id},
#                 confidence_score=0.6
#             )
#             insights_created += 1
        
#         # 5. Interest Change Insight (if topics are discussed)
#         if conversation.topics_discussed and len(conversation.topics_discussed) > 0:
#             ConversationInsight.objects.create(
#                 senior=senior,
#                 insight_type='interest_change',
#                 title='Topics of Interest',
#                 description=f'Recent conversation focused on: {", ".join(conversation.topics_discussed)}',
#                 data={'topics': conversation.topics_discussed, 'conversation_id': conversation.id},
#                 confidence_score=0.6
#             )
#             insights_created += 1
        
#         # 6. Default insight if no specific patterns detected
#         if insights_created == 0:
#             ConversationInsight.objects.create(
#                 senior=senior,
#                 insight_type='communication_style',
#                 title='Conversation Recorded',
#                 description=f'Conversation with {senior.name} was recorded and analyzed',
#                 data={'conversation_id': conversation.id, 'summary_length': len(conversation.summary)},
#                 confidence_score=0.5
#             )
#             insights_created += 1
        
#         logger.info(f"Generated {insights_created} insights for {senior.name}")
        
#     except Exception as e:
#         logger.error(f"Error generating insights for {senior.name}: {str(e)}")



