#!/usr/bin/env python3
"""
Android Handler - AI-powered conversation handler for open threads.
Polls open_thread table and responds to messages using rule-based templates or OpenAI GPT.
"""
import os
import re
import time
import datetime as dt
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from pathlib import Path
from supabase import create_client
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from workflows.deferral_parser import parse_deferral_text

# Import tools module
try:
    from utils.tools import (
        resolve_agreement_id, log_message, open_thread, close_thread,
        schedule_followup, book_appt_request, get_equity_snapshot,
        sanitize_internal_codes
    )
    TOOLS_AVAILABLE = True
except ImportError:
    print("⚠️  tools module not found, some functions will be limited")
    TOOLS_AVAILABLE = False
    def sanitize_internal_codes(text):
        return text

# OpenAI is optional
try:
    from openai import OpenAI
    OPENAI_AVAILABLE = True
except ImportError:
    print("⚠️  OpenAI not available, using template-only mode")
    OPENAI_AVAILABLE = False


def load_lia_system_prompt() -> str:
    """
    Load Lia's comprehensive Mode B system prompt from file.
    
    Returns:
        Full system prompt text
    """
    # Try multiple paths to handle different execution contexts
    script_dir = Path(__file__).parent
    possible_paths = [
        script_dir / "lia_system_prompt.txt",  # Same directory as this script
        Path("android/lia_system_prompt.txt"),  # From project root
    ]
    
    for prompt_path in possible_paths:
        if prompt_path.exists():
            return prompt_path.read_text(encoding="utf-8")
    
    # Fallback to basic prompt if file not found
    print("⚠️  lia_system_prompt.txt not found, using fallback prompt")
    return """You are Lia, the Lincoln Audi Loyalty WhatsApp Assistant.
Be helpful, professional, low-pressure. Use UK spelling.
Never quote prices or payments. Encourage appointments (visit or call).
Keep messages short (1-3 paragraphs)."""


def get_client():
    """Create a single Supabase client from environment variables."""
    supabase_url = os.getenv('SUPABASE_URL')
    supabase_key = os.getenv('SUPABASE_SERVICE_KEY')
    
    if not supabase_url or not supabase_key:
        raise ValueError("SUPABASE_URL and SUPABASE_SERVICE_KEY must be set")
    
    return create_client(supabase_url, supabase_key)


def ok_to_reply(now=None):
    """
    Check if replies are allowed (06:00–23:59:59 UK time).
    
    Args:
        now: Optional datetime to check (defaults to current UK time)
    
    Returns:
        True if within reply window, False otherwise
    """
    tz = ZoneInfo("Europe/London")
    now = now or dt.datetime.now(tz)
    # Replies allowed 06:00–00:00 (midnight)
    return dt.time(6, 0) <= now.time() <= dt.time(23, 59, 59)


def is_opt_out_message(text: str) -> bool:
    """
    Check if message is an opt-out request.
    
    Args:
        text: Message text
    
    Returns:
        True if message matches opt-out keywords
    """
    if not text:
        return False
    
    # Opt-out keywords (case-insensitive, trim whitespace)
    opt_out_keywords = [
        "stop", "stop.", "stop!",
        "opt out", "unsubscribe", "remove me",
        "no more", "do not contact", "don't contact"
    ]
    
    # Normalize text: lowercase and trim
    normalized = text.strip().lower()
    
    # Check exact match or contains keyword
    return normalized in opt_out_keywords or any(kw in normalized for kw in opt_out_keywords)


def check_opt_out_status(agreement_number: str, channel: str = 'whatsapp') -> bool:
    """
    Check if customer has opted out within last 2 years.
    
    Args:
        agreement_number: Agreement number
        channel: Communication channel (default: whatsapp)
    
    Returns:
        True if opted out (suppress sending), False otherwise
    """
    try:
        client = get_client()
        
        # Calculate 2 years ago
        tz = ZoneInfo("Europe/London")
        now = dt.datetime.now(tz)
        two_years_ago = now - timedelta(days=730)
        
        # Query opt_out table
        result = client.table('opt_out') \
            .select('opted_out_at') \
            .eq('agreement_number', agreement_number) \
            .eq('channel', channel) \
            .gte('opted_out_at', two_years_ago.isoformat()) \
            .execute()
        
        return len(result.data) > 0 if result.data else False
    
    except Exception as e:
        print(f"   ⚠️  Error checking opt-out status: {e}")
        return False


def handle_opt_out(agreement_number: str, inbound_text: str, channel: str = 'whatsapp') -> bool:
    """
    Handle opt-out request: insert to opt_out table, log, send confirmation, close thread.
    
    Args:
        agreement_number: Agreement number
        inbound_text: Customer's opt-out message
        channel: Communication channel (default: whatsapp)
    
    Returns:
        True if opt-out handled successfully
    """
    try:
        client = get_client()
        tz = ZoneInfo("Europe/London")
        now = dt.datetime.now(tz)
        
        print(f"   🛑 OPT-OUT detected")
        
        # 1. Insert into opt_out table
        opt_out_data = {
            'agreement_number': agreement_number,
            'channel': channel,
            'opted_out_at': now.isoformat()
        }
        client.table('opt_out').insert(opt_out_data).execute()
        print(f"   ✅ Inserted to opt_out table")
        
        # 2. Log customer's opt-out message to contact_history
        if TOOLS_AVAILABLE:
            log_message(
                agreement_ref=agreement_number,
                channel=channel,
                direction='inbound',
                message_text=inbound_text,
                intent_label='opt_out',
                template_slug='opt_out',
                source='customer',
                model=None
            )
        print(f"   ✅ Logged opt-out message")
        
        # 3. Send confirmation message (if within reply hours)
        if ok_to_reply(now):
            confirmation_text = (
                "Got it — I'll stop messages on this channel. "
                "If you ever want to hear from us again, just say 'start'."
            )
            
            if TOOLS_AVAILABLE:
                log_message(
                    agreement_ref=agreement_number,
                    channel=channel,
                    direction='outbound',
                    message_text=confirmation_text,
                    intent_label='opt_out_ack',
                    template_slug='opt_out_ack',
                    source='ai',
                    model=None
                )
            print(f"   ✅ Sent opt-out confirmation")
        
        # 4. Close the open thread
        if TOOLS_AVAILABLE:
            close_thread(agreement_number)
            print(f"   ✅ Thread closed")
        
        return True
    
    except Exception as e:
        print(f"   ❌ Error handling opt-out: {e}")
        return False


def parse_defer_time(text: str, now=None):
    """
    Parse customer deferral time from text.
    
    Args:
        text: Customer message text
        now: Current datetime (defaults to UK time now)
    
    Returns:
        Tuple of (due_at_dt, label) or (None, None)
    """
    if not text:
        return None, None
    
    tz = ZoneInfo("Europe/London")
    now = now or dt.datetime.now(tz)
    text_lower = text.lower()
    
    # Pattern: 'in X month(s)'
    match = re.search(r'in\s+(\d+)\s+month', text_lower)
    if match:
        months = int(match.group(1))
        if 1 <= months <= 12:
            due_at = now + timedelta(days=30 * months)
            return due_at, f"in {months} month{'s' if months > 1 else ''}"
    
    # Pattern: 'in X week(s)'
    match = re.search(r'in\s+(\d+)\s+week', text_lower)
    if match:
        weeks = int(match.group(1))
        if 1 <= weeks <= 12:
            due_at = now + timedelta(days=7 * weeks)
            return due_at, f"in {weeks} week{'s' if weeks > 1 else ''}"
    
    # Pattern: 'few months' or 'couple of months'
    if 'few months' in text_lower or 'couple of months' in text_lower:
        due_at = now + timedelta(days=60)
        return due_at, "few months"
    
    # Pattern: 'next month'
    if 'next month' in text_lower:
        # First business day of next month at 09:00
        next_month = (now.replace(day=1) + timedelta(days=32)).replace(day=1)
        due_at = next_month.replace(hour=9, minute=0, second=0, microsecond=0)
        return due_at, "next month"
    
    # Pattern: explicit months
    month_patterns = {
        'january': 1, 'jan': 1,
        'february': 2, 'feb': 2,
        'march': 3, 'mar': 3,
        'april': 4, 'apr': 4,
        'may': 5,
        'june': 6, 'jun': 6,
        'july': 7, 'jul': 7,
        'august': 8, 'aug': 8,
        'september': 9, 'sep': 9, 'sept': 9,
        'october': 10, 'oct': 10,
        'november': 11, 'nov': 11,
        'december': 12, 'dec': 12
    }
    
    for month_name, month_num in month_patterns.items():
        if month_name in text_lower:
            # Find next occurrence of this month
            target_year = now.year
            if month_num <= now.month:
                target_year += 1
            due_at = dt.datetime(target_year, month_num, 1, 9, 0, 0, tzinfo=tz)
            return due_at, f"{month_name.capitalize()}"
    
    return None, None


def detect_life_events(text: str):
    """
    Detect life events from customer message.
    
    Args:
        text: Customer message text
    
    Returns:
        List of dicts with note_type and note_text
    """
    if not text:
        return []
    
    text_lower = text.lower()
    events = []
    
    # Moving house
    if any(word in text_lower for word in ['moving', 'move', 'house', 'remortgage']):
        events.append({
            'note_type': 'moving_house',
            'note_text': 'Customer moving house soon'
        })
    
    # New baby
    if any(word in text_lower for word in ['baby', 'pregnant', 'newborn']):
        events.append({
            'note_type': 'new_baby',
            'note_text': 'New baby mentioned'
        })
    
    # Job change
    if any(word in text_lower for word in ['job', 'promotion', 'redundant']):
        events.append({
            'note_type': 'job_change',
            'note_text': 'Job change mentioned'
        })
    
    # Health
    if any(word in text_lower for word in ['surgery', 'operation', 'hospital']):
        events.append({
            'note_type': 'health',
            'note_text': 'Health/operation mentioned'
        })
    
    # Holiday/occasion
    if any(word in text_lower for word in ['holiday', 'honeymoon', 'wedding', 'anniversary']):
        events.append({
            'note_type': 'holiday',
            'note_text': 'Holiday/occasion mentioned'
        })
    
    return events


def parse_when(text: str) -> dict:
    """
    Parse simple UK date/time phrases from text.
    
    Args:
        text: Customer message text
    
    Returns:
        Dict with date_uk and time_uk keys (values may be None)
    """
    if not text:
        return {'date_uk': None, 'time_uk': None}
    
    text_lower = text.lower().strip()
    result = {'date_uk': None, 'time_uk': None}
    
    # UK date phrases
    if 'tomorrow' in text_lower:
        result['date_uk'] = 'tomorrow'
    elif 'monday' in text_lower:
        result['date_uk'] = 'Monday'
    elif 'tuesday' in text_lower:
        result['date_uk'] = 'Tuesday'
    elif 'wednesday' in text_lower:
        result['date_uk'] = 'Wednesday'
    elif 'thursday' in text_lower:
        result['date_uk'] = 'Thursday'
    elif 'friday' in text_lower:
        result['date_uk'] = 'Friday'
    elif 'saturday' in text_lower:
        result['date_uk'] = 'Saturday'
    elif 'sunday' in text_lower:
        result['date_uk'] = 'Sunday'
    elif 'next week' in text_lower:
        result['date_uk'] = 'next week'
    
    # UK time phrases
    time_patterns = [
        (r'\b(\d{1,2}):(\d{2})\b', lambda m: f"{m.group(1)}:{m.group(2)}"),  # 10:30
        (r'\bhalf ten\b', lambda m: '10:30'),
        (r'\bhalf eleven\b', lambda m: '11:30'),
        (r'\bmidday\b', lambda m: '12:00'),
        (r'\bnoon\b', lambda m: '12:00'),
        (r'\bafter work\b', lambda m: 'after work'),
        (r'\bmorning\b', lambda m: 'morning'),
        (r'\bafternoon\b', lambda m: 'afternoon'),
        (r'\bevening\b', lambda m: 'evening'),
    ]
    
    for pattern, extractor in time_patterns:
        match = re.search(pattern, text_lower)
        if match:
            result['time_uk'] = extractor(match)
            break
    
    return result


def is_positive_intent(text: str) -> bool:
    """
    Detect positive-intent booking keywords (UK phrasing).
    
    Args:
        text: Customer message text
    
    Returns:
        True if positive intent detected
    """
    if not text:
        return False
    
    text_lower = text.lower().strip()
    
    positive_keywords = [
        'yes', 'yeah', 'yep', 'sure', 'ok', 'okay',
        'keen', 'interested', 'book', 'come in',
        'when can i pop in', 'pop in', 'visit',
        "let's do it", 'lets do it', 'ok to book',
        'sounds good', 'perfect', 'brilliant'
    ]
    
    return any(kw in text_lower for kw in positive_keywords)


def upsert_conversation_summary(agreement_id: int, last_intent: str, summary_text: str, tokens_used: int = None):
    """
    Upsert conversation summary.
    
    Args:
        agreement_id: Agreement ID
        last_intent: Last detected intent
        summary_text: Summary text
        tokens_used: Optional token count
    """
    try:
        client = get_client()
        
        upsert_data = {
            'agreement_id': agreement_id,
            'last_summary': summary_text,
            'last_intent': last_intent,
            'last_updated': dt.datetime.utcnow().isoformat()
        }
        
        if tokens_used is not None:
            upsert_data['tokens_used'] = tokens_used
        
        client.table('conversation_summary').upsert(upsert_data).execute()
        print(f"   📝 SUMMARY upserted")
    
    except Exception as e:
        print(f"   ⚠️  Error upserting summary: {e}")


def insert_relationship_notes(agreement_id: int, notes: list):
    """
    Insert relationship notes.
    
    Args:
        agreement_id: Agreement ID
        notes: List of dicts with note_type and note_text
    """
    if not notes:
        return
    
    try:
        client = get_client()
        
        for note in notes:
            insert_data = {
                'agreement_id': agreement_id,
                'note_type': note['note_type'],
                'note_text': note['note_text'],
                'confidence': 0.8
            }
            
            client.table('relationship_note').insert(insert_data).execute()
            print(f"   🔖 REL-NOTE: {note['note_type']} - {note['note_text']}")
    
    except Exception as e:
        print(f"   ⚠️  Error inserting relationship notes: {e}")


def generate_conversation_summary(agreement_id: int, last_intent: str):
    """
    Generate a summary of the conversation.
    
    Args:
        agreement_id: Agreement ID
        last_intent: Last detected intent
    
    Returns:
        Summary text
    """
    try:
        client = get_client()
        
        # Get last 10 messages
        result = client.table('contact_history') \
            .select('direction, message_text, intent_label') \
            .eq('agreement_id', agreement_id) \
            .order('happened_at', desc=True) \
            .limit(10) \
            .execute()
        
        if not result.data:
            return "No conversation history"
        
        messages = list(reversed(result.data))
        
        # Try OpenAI summary if available
        if OPENAI_AVAILABLE:
            api_key = os.getenv('OPENAI_API_KEY')
            if api_key:
                try:
                    openai_client = OpenAI(api_key=api_key)
                    
                    # Build conversation text
                    conv_text = "\n".join([
                        f"{m['direction']}: {m.get('message_text', '(no text)')}"
                        for m in messages
                        if m.get('message_text')
                    ])
                    
                    response = openai_client.chat.completions.create(
                        model="gpt-4o-mini",
                        messages=[
                            {"role": "system", "content": "Summarize this customer conversation in 2-3 bullet points. Use UK English, include any next steps."},
                            {"role": "user", "content": conv_text}
                        ],
                        max_tokens=150
                    )
                    
                    return response.choices[0].message.content.strip()
                
                except Exception as e:
                    print(f"   ⚠️  OpenAI summary error: {e}")
        
        # Fallback: rule-based summary
        intents = [m.get('intent_label') for m in messages if m.get('intent_label')]
        summary_parts = []
        
        if last_intent:
            summary_parts.append(f"Last intent: {last_intent}")
        
        if 'not_ready_yet' in intents or 'openai_fallback' in intents:
            summary_parts.append("Customer deferred")
        
        return ". ".join(summary_parts) if summary_parts else "Conversation ongoing"
    
    except Exception as e:
        print(f"   ⚠️  Error generating summary: {e}")
        return "Summary unavailable"


def load_rules_and_templates(client):
    """
    Load objection rules and reply templates from Supabase (per poll cycle).
    
    Returns:
        Tuple of (rules_list, templates_dict)
        - rules: List of dicts with {label, keywords, template_slug, priority, active, match_any}
        - templates: Dict of {slug: {body, max_len, purpose, tone, channel, active}}
    """
    try:
        # Fetch active rules ordered by priority (lower number wins)
        rules_result = client.table('objection_rule') \
            .select('label, keywords, template_slug, priority, active, match_any') \
            .eq('active', True) \
            .order('priority', desc=False) \
            .execute()
        
        rules = rules_result.data if rules_result.data else []
        
        # Fetch active templates
        templates_result = client.table('reply_template') \
            .select('slug, body, max_len, purpose, tone, channel, active') \
            .eq('active', True) \
            .execute()
        
        templates_data = templates_result.data if templates_result.data else []
        templates = {t['slug']: t for t in templates_data}
        
        return rules, templates
    
    except Exception as e:
        print(f"⚠️  Error fetching rules/templates: {e}")
        return [], {}


def fetch_rules_and_templates(client):
    """
    Legacy wrapper for compatibility - calls load_rules_and_templates.
    """
    rules, templates = load_rules_and_templates(client)
    # Convert templates dict to simple {slug: body} format for legacy code
    simple_templates = {slug: tpl.get('body', '') if isinstance(tpl, dict) else tpl 
                       for slug, tpl in templates.items()}
    return rules, simple_templates


def fetch_customer_context(agreement_number):
    """
    Query v_agreement_latest to get customer and vehicle data.
    
    Returns:
        Dict with: first_name, last_name, vehicle_model, registration, agreement_number
    """
    try:
        client = get_client()
        
        # Query v_agreement_latest view
        result = client.table('v_agreement_latest') \
            .select('first_name, last_name, registration, make, model, agreement_number') \
            .eq('agreement_number', agreement_number) \
            .execute()
        
        if not result.data or len(result.data) == 0:
            return {
                'first_name': '',
                'last_name': '',
                'vehicle_model': '',
                'registration': '',
                'agreement_number': agreement_number
            }
        
        row = result.data[0]
        
        # Combine make + model for vehicle_model
        make = row.get('make', '')
        model = row.get('model', '')
        vehicle_model = f"{make} {model}".strip() if make or model else ''
        
        return {
            'first_name': row.get('first_name', ''),
            'last_name': row.get('last_name', ''),
            'vehicle_model': vehicle_model,
            'registration': row.get('registration', ''),
            'agreement_number': row.get('agreement_number', agreement_number)
        }
    
    except Exception as e:
        print(f"⚠️  Error fetching customer context: {e}")
        return {
            'first_name': '',
            'last_name': '',
            'vehicle_model': '',
            'registration': '',
            'agreement_number': agreement_number
        }


def personalise(template_str, ctx):
    """
    Replace template variables with customer context.
    
    Variables: {{first_name}}, {{last_name}}, {{vehicle_model}}, {{registration}}
    
    Args:
        template_str: Template string with {{variables}}
        ctx: Context dict with customer data
    
    Returns:
        Personalized string
    """
    result = template_str
    result = result.replace('{{first_name}}', ctx.get('first_name', ''))
    result = result.replace('{{last_name}}', ctx.get('last_name', ''))
    result = result.replace('{{vehicle_model}}', ctx.get('vehicle_model', ''))
    result = result.replace('{{registration}}', ctx.get('registration', ''))
    return result


def match_rule(text: str, rules: list) -> dict | None:
    """
    Match inbound text against objection rules using keywords array.
    
    Args:
        text: Inbound message text
        rules: List of rule dicts with {label, keywords, template_slug, priority, match_any}
    
    Returns:
        Matched rule dict or None
    """
    if not text or not rules:
        return None
    
    # Normalize text: lowercase
    t = text.lower()
    
    # Check each rule in priority order (already sorted by priority ascending)
    for r in rules:
        kws = [k.lower() for k in (r.get('keywords') or [])]
        if not kws:
            continue
        
        # Check match_any flag (default True)
        if r.get('match_any', True):
            # Match if ANY keyword is found
            if any(k in t for k in kws):
                return r
        else:
            # Match if ALL keywords are found
            if all(k in t for k in kws):
                return r
    
    return None


def render_template(body: str, context: dict) -> str:
    """
    Render template by replacing {{variables}} with context values.
    
    Args:
        body: Template string with {{variables}}
        context: Dict with customer data
    
    Returns:
        Rendered string
    """
    out = body or ''
    out = out.replace('{{first_name}}', context.get('first_name') or '')
    out = out.replace('{{vehicle_model}}', context.get('vehicle_model') or '')
    return out.strip()


def get_latest_contact(supabase, agreement_id):
    """Get the most recent contact history entry for an agreement."""
    result = supabase.table('contact_history') \
        .select('*') \
        .eq('agreement_id', agreement_id) \
        .order('happened_at', desc=True) \
        .limit(1) \
        .execute()
    
    if result.data and len(result.data) > 0:
        return result.data[0]
    return None


def should_reply(latest_contact):
    """Check if we should reply (only if last message was inbound)."""
    if not latest_contact:
        return False
    return latest_contact.get('direction') == 'inbound'


def generate_reply(agreement_number, inbound_text, client):
    """
    Generate reply using rule matching first, then OpenAI fallback.
    
    Args:
        agreement_number: Customer agreement number
        inbound_text: The inbound message text
        client: Supabase client
    
    Returns:
        Dict with keys: reply_text, method, intent_label, template_slug, model, service_handoff
    """
    # Fetch customer context
    ctx = fetch_customer_context(agreement_number)
    
    # Load rules and templates (full objects with metadata)
    rules, templates = load_rules_and_templates(client)
    
    # Try to match a rule
    matched_rule = match_rule(inbound_text, rules) if inbound_text else None
    
    if matched_rule:
        template_slug = matched_rule.get('template_slug')
        intent_label = matched_rule.get('label')
        tpl = templates.get(template_slug)
        
        if tpl:
            # Render template with customer context
            msg = render_template(tpl.get('body', ''), ctx)
            
            # Enforce max_len if specified
            max_len = tpl.get('max_len')
            if max_len and len(msg) > max_len:
                msg = msg[:max_len - 1] + '…'
            
            print(f"   📋 Matched rule → template: {template_slug}")
            print(f"   [RULE] label={intent_label} -> {template_slug}")
            
            # Check for service handoff - use service_book_qualify template
            service_handoff = False
            if intent_label in ('service_only', 'book_service') or template_slug in ('service_only', 'book_service'):
                service_handoff = True
                # Override template to use service_book_qualify for service intents
                service_tpl = templates.get('service_book_qualify')
                if service_tpl:
                    msg = render_template(service_tpl.get('body', ''), ctx)
                    template_slug = 'service_book_qualify'
                    print(f"   🔧 Service intent detected → using service_book_qualify template")
            
            return {
                'reply_text': msg,
                'method': 'template',
                'intent_label': intent_label,
                'template_slug': template_slug,
                'model': None,
                'service_handoff': service_handoff
            }
    
    # No rule matched, try OpenAI fallback
    print(f"   🤖 No rule matched → trying OpenAI")
    print(f"   [OPENAI] fallback used")
    
    if OPENAI_AVAILABLE:
        api_key = os.getenv('OPENAI_API_KEY')
        if api_key:
            try:
                openai_client = OpenAI(api_key=api_key)
                
                # Load Lia's Mode B system prompt
                system_prompt = load_lia_system_prompt()
                
                # Build user prompt with context
                first_name = ctx.get('first_name', 'Customer')
                vehicle_model = ctx.get('vehicle_model', 'their vehicle')
                registration = ctx.get('registration', '')
                
                user_content = f"""Customer: {first_name}
Vehicle: {vehicle_model} ({registration})
Inbound message: "{inbound_text}"

Respond as Lia from Lincoln Audi - friendly, helpful UK dealership tone. Keep it short (1-3 paragraphs)."""
                
                response = openai_client.chat.completions.create(
                    model="gpt-4o-mini",
                    messages=[
                        {"role": "system", "content": system_prompt},
                        {"role": "user", "content": user_content}
                    ],
                    max_tokens=200,
                    temperature=0.8
                )
                
                reply_text = response.choices[0].message.content.strip()
                print(f"   ✅ OpenAI generated reply")
                
                return {
                    'reply_text': reply_text,
                    'method': 'openai',
                    'intent_label': 'openai_fallback',
                    'template_slug': None,
                    'model': 'gpt-4o-mini',
                    'service_handoff': False
                }
            
            except Exception as e:
                print(f"   ⚠️  OpenAI error: {e}")
    
    # Ultimate fallback: use ack_hello template or generic message
    simple_rules, simple_templates = fetch_rules_and_templates(client)
    fallback_template = simple_templates.get('ack_hello', "Hi {{first_name}}, it's Lia at Lincoln Audi. How can I help?")
    reply_text = personalise(fallback_template, ctx)
    print(f"   💬 Using fallback template")
    
    return {
        'reply_text': reply_text,
        'method': 'fallback',
        'intent_label': 'generic_fallback',
        'template_slug': 'ack_hello',
        'model': None,
        'service_handoff': False
    }


def send_outbound_message(agreement_number, reply_data, channel='whatsapp'):
    """
    Log outbound message with full metadata using tools.log_message.
    Checks opt-out status before sending.
    Sanitizes internal codes from message text.
    
    Args:
        agreement_number: Customer agreement number
        reply_data: Dict with reply_text, intent_label, template_slug, model
        channel: Communication channel (default: whatsapp)
    
    Returns:
        True if successful, False otherwise
    """
    try:
        # Check opt-out status (suppress if opted out within last 2 years)
        if check_opt_out_status(agreement_number, channel):
            print(f"   🛑 Suppressed (customer opted out)")
            return False
        
        # Sanitize internal codes from reply text (last-resort safety)
        clean_reply = sanitize_internal_codes(reply_data['reply_text'])
        
        if TOOLS_AVAILABLE:
            log_message(
                agreement_ref=agreement_number,
                channel=channel,
                direction='outbound',
                message_text=clean_reply,
                intent_label=reply_data.get('intent_label'),
                template_slug=reply_data.get('template_slug'),
                source='ai',
                model=reply_data.get('model')
            )
        else:
            # Fallback to direct Supabase insert
            client = get_client()
            agreement_id = resolve_agreement_id(agreement_number)
            if not agreement_id:
                print(f"   ⚠️  Could not resolve agreement_id for {agreement_number}")
                return False
            
            insert_data = {
                'agreement_id': agreement_id,
                'channel': channel,
                'direction': 'outbound',
                'message_text': clean_reply,
                'intent_label': reply_data.get('intent_label'),
                'template_slug': reply_data.get('template_slug'),
                'source': 'ai',
                'model': reply_data.get('model')
            }
            client.table('contact_history').insert(insert_data).execute()
        
        return True
    
    except Exception as e:
        print(f"   ❌ Error sending outbound: {e}")
        return False


def update_thread_timestamp(supabase, thread_id):
    """Update the open_thread's updated_at timestamp."""
    try:
        update_data = {
            'updated_at': dt.datetime.utcnow().isoformat()
        }
        
        supabase.table('open_thread') \
            .update(update_data) \
            .eq('id', thread_id) \
            .execute()
    except Exception as e:
        print(f"   ⚠️  Error updating thread timestamp: {e}")


def process_open_threads():
    """Main processing loop - handle all open threads."""
    try:
        client = get_client()
        
        # Get all open threads
        result = client.table('open_thread') \
            .select('*') \
            .eq('status', 'open') \
            .execute()
        
        if not result.data or len(result.data) == 0:
            print("💤 No open threads found")
            return
        
        print(f"📋 Found {len(result.data)} open thread(s)")
        
        for thread in result.data:
            thread_id = thread.get('id')
            agreement_number = thread.get('agreement_number')
            
            try:
                print(f"\n🔍 Processing thread: {agreement_number}")
                
                # Resolve agreement_id
                agreement_id = None
                if TOOLS_AVAILABLE:
                    agreement_id = resolve_agreement_id(agreement_number)
                else:
                    agreement_result = client.table('agreement') \
                        .select('id') \
                        .eq('agreement_number', agreement_number) \
                        .execute()
                    if agreement_result.data:
                        agreement_id = agreement_result.data[0]['id']
                
                if not agreement_id:
                    print(f"   ⚠️  Agreement {agreement_number} not found, skipping")
                    continue
                
                # Get latest contact
                latest_contact = get_latest_contact(client, agreement_id)
                
                # Check if we should reply
                if not should_reply(latest_contact):
                    print(f"   ⏭️  Skipping (last message was outbound)")
                    continue
                
                # Get inbound message text (if available)
                inbound_text = latest_contact.get('message_text', '') if latest_contact else ''
                
                # Check for opt-out BEFORE objections/deferrals
                if inbound_text and is_opt_out_message(inbound_text):
                    handle_opt_out(agreement_number, inbound_text, channel='whatsapp')
                    continue  # Skip further processing - thread is closed
                
                # Process inbound message for life events and deferrals
                tz = ZoneInfo("Europe/London")
                now = dt.datetime.now(tz)
                
                # Check for appointment-related messages
                # First, check if there's an existing appointment_request with status='new'
                existing_appt = client.table('appointment_request') \
                    .select('*') \
                    .eq('agreement_id', agreement_id) \
                    .eq('status', 'new') \
                    .order('created_at', desc=True) \
                    .limit(1) \
                    .execute()
                
                # Parse date/time from message using NEW safe parser
                from deferral_parser import parse_appointment_when
                parsed = parse_appointment_when(inbound_text) if inbound_text else {'parsed': False}
                
                # Check if message has time/date keywords
                time_keywords = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
                                'tomorrow', 'today', 'next week', 'morning', 'afternoon', 'evening', 
                                'am', 'pm', ':', 'o\'clock', 'oclock']
                has_time_reference = any(kw in inbound_text.lower() for kw in time_keywords) if inbound_text else False
                
                # If there's an existing appointment and message has time reference, update it
                if inbound_text and existing_appt.data and has_time_reference:
                    print(f"   📅 DATE/TIME detected for existing appointment")
                    
                    appt_id = existing_appt.data[0]['id']
                    
                    # Update appointment with parsed values
                    update_data = {
                        'requested_text': inbound_text
                    }
                    
                    if parsed.get('date'):
                        update_data['preferred_date'] = parsed['date']
                    if parsed.get('time'):
                        update_data['preferred_time'] = parsed['time']
                    
                    client.table('appointment_request') \
                        .update(update_data) \
                        .eq('id', appt_id) \
                        .execute()
                    
                    print(f"   ✅ Updated appointment request")
                    if parsed.get('date') or parsed.get('time'):
                        print(f"      ✓ Parsed: date={parsed.get('date')}, time={parsed.get('time')}")
                    else:
                        print(f"      ⚠️  Could not parse '{inbound_text}' - stored in requested_text only")
                        
                        # Create manager_task if parsing failed
                        task_data = {
                            'agreement_id': agreement_id,
                            'task_type': 'confirm_appointment',
                            'title': 'Confirm appointment time',
                            'description': f'Customer requested: "{inbound_text}"\nNeed to confirm specific date/time.',
                            'priority': 'high',
                            'status': 'pending'
                        }
                        client.table('manager_task').insert(task_data).execute()
                        print(f"      📋 Created manager_task for manual follow-up")
                    
                    # Log inbound as positive_intent
                    if TOOLS_AVAILABLE:
                        log_message(
                            agreement_ref=agreement_number,
                            channel='whatsapp',
                            direction='inbound',
                            message_text=inbound_text,
                            intent_label='positive_intent',
                            template_slug=None,
                            source='customer',
                            model=None
                        )
                    
                    # Send confirmation with when_summary (use original text or parsed parts)
                    when_str = inbound_text
                    
                    # Get appt_confirm_hold template
                    template_result = client.table('reply_template') \
                        .select('body') \
                        .eq('slug', 'appt_confirm_hold') \
                        .eq('active', True) \
                        .execute()
                    
                    if template_result.data and ok_to_reply():
                        confirm_text = template_result.data[0]['body'].replace('{{when_summary}}', when_str)
                        
                        if TOOLS_AVAILABLE:
                            log_message(
                                agreement_ref=agreement_number,
                                channel='whatsapp',
                                direction='outbound',
                                message_text=confirm_text,
                                intent_label='appt_confirm_hold',
                                template_slug='appt_confirm_hold',
                                source='ai',
                                model=None
                            )
                        
                        print(f"   ✅ Sent appointment hold confirmation")
                    
                    # Keep thread open, skip further processing
                    continue
                
                # Check for positive-intent booking (new appointment creation)
                elif inbound_text and is_positive_intent(inbound_text) and not existing_appt.data:
                    print(f"   ✅ POSITIVE-INTENT detected")
                    
                    # Log inbound as positive_intent
                    if TOOLS_AVAILABLE:
                        log_message(
                            agreement_ref=agreement_number,
                            channel='whatsapp',
                            direction='inbound',
                            message_text=inbound_text,
                            intent_label='positive_intent',
                            template_slug=None,
                            source='customer',
                            model=None
                        )
                    
                    # Check last outbound intent to decide on reply template
                    last_outbound_intent = None
                    last_outbound = client.table('contact_history') \
                        .select('intent_label') \
                        .eq('agreement_id', agreement_id) \
                        .eq('direction', 'outbound') \
                        .order('happened_at', desc=True) \
                        .limit(1) \
                        .execute()
                    
                    if last_outbound.data:
                        last_outbound_intent = last_outbound.data[0].get('intent_label')
                        print(f"   📋 Last outbound intent: {last_outbound_intent}")
                    
                    # Only use appt_qualify if last outbound was campaign/renewal/anniversary
                    campaign_intents = ['campaign_outreach', 'renewal_invite', 'anniversary_intro']
                    should_qualify = last_outbound_intent in campaign_intents
                    
                    if should_qualify:
                        # Create new appointment_request with safe parsing
                        if TOOLS_AVAILABLE:
                            appt = book_appt_request(
                                agreement_number=agreement_number,
                                requested_by='ai',
                                channel='whatsapp',
                                notes='Prospect agreed to book (auto)',
                                requested_text=inbound_text
                            )
                            appt_id = appt.get('id')
                            print(f"   📅 Created appointment request: {appt_id}")
                            
                            # Check if parsing succeeded
                            if appt.get('preferred_date') or appt.get('preferred_time'):
                                print(f"      ✓ Parsed: date={appt.get('preferred_date')}, time={appt.get('preferred_time')}")
                            else:
                                print(f"      ⚠️  Could not parse '{inbound_text}' - manager_task created")
                        
                        # Send appt_qualify template (if within reply window)
                        if ok_to_reply():
                            template_result = client.table('reply_template') \
                                .select('body') \
                                .eq('slug', 'appt_qualify') \
                                .eq('active', True) \
                                .execute()
                            
                            if template_result.data:
                                qualify_text = template_result.data[0]['body']
                                
                                if TOOLS_AVAILABLE:
                                    log_message(
                                        agreement_ref=agreement_number,
                                        channel='whatsapp',
                                        direction='outbound',
                                        message_text=qualify_text,
                                        intent_label='appt_qualify',
                                        template_slug='appt_qualify',
                                        source='ai',
                                        model=None
                                    )
                                
                                print(f"   ✅ Sent appointment qualifier")
                        
                        # Keep thread open, skip further processing
                        continue
                    else:
                        print(f"   ⚠️  Last outbound not campaign-related, using normal reply flow")
                
                if inbound_text:
                    # Detect life events
                    events = detect_life_events(inbound_text)
                    if events:
                        insert_relationship_notes(agreement_id, events)
                    
                    # Parse deferral text using new parser
                    deferral_result = parse_deferral_text(inbound_text, now)
                    if deferral_result and deferral_result.get("due_at") and TOOLS_AVAILABLE:
                        # Schedule follow-up
                        fu = schedule_followup(
                            agreement_number,
                            deferral_result["due_at"],
                            reason=f"Deferral: {deferral_result.get('reason', '(unspecified)')}"
                        )
                        print(f"   📅 FOLLOW-UP scheduled: {deferral_result['due_at']} — {deferral_result.get('reason')} (confidence: {deferral_result.get('confidence')})")
                        
                        # Log parser detection as internal note
                        log_message(
                            agreement_ref=agreement_number,
                            channel="whatsapp",
                            direction="outbound",
                            message_text=f"[auto-follow-up] {fu.get('due_at')} — {fu.get('reason')} (conf {deferral_result.get('confidence')})",
                            intent_label="deferral_parsed",
                            template_slug="deferral_parser",
                            source="ai",
                            model="parser"
                        )
                        
                        # Send confirmation if within reply window
                        if ok_to_reply():
                            confirm_text = (
                                "No problem — I'll set a reminder and get in touch then. "
                                "If anything changes before that, just drop me a message here."
                            )
                            log_message(
                                agreement_ref=agreement_number,
                                channel="whatsapp",
                                direction="outbound",
                                message_text=confirm_text,
                                intent_label="deferral_ack",
                                template_slug="deferral_parser_ack",
                                source="ai",
                                model=None
                            )
                            print(f"   ✅ Sent deferral confirmation")
                
                # Check if we're in reply time window
                if not ok_to_reply():
                    print(f"   ⏰ Outside reply window (06:00-23:59:59), deferring until tomorrow 06:05")
                    if TOOLS_AVAILABLE:
                        # Schedule followup for next day at 06:05
                        tomorrow = dt.datetime.now(tz) + timedelta(days=1)
                        followup_time = tomorrow.replace(hour=6, minute=5, second=0, microsecond=0)
                        schedule_followup(agreement_number, followup_time.isoformat(), reason="Reply deferred out of hours")
                    continue
                
                # Generate reply
                print(f"   💬 Generating reply...")
                reply_data = generate_reply(agreement_number, inbound_text, client)
                
                # Handle service handoff if needed
                if reply_data.get('service_handoff') and TOOLS_AVAILABLE:
                    appt = book_appt_request(
                        agreement_number=agreement_number,
                        requested_by='ai',
                        channel='whatsapp',
                        notes='Service booking request via Lia'
                    )
                    print(f"   🔧 [SERVICE] booking request opened id={appt.get('id')}")
                
                # Send outbound message (respect ok_to_reply)
                if ok_to_reply():
                    if send_outbound_message(agreement_number, reply_data, channel='whatsapp'):
                        print(f"   ✅ Sent {reply_data['method']} reply to {agreement_number}")
                        
                        # Update thread timestamp
                        update_thread_timestamp(client, thread_id)
                        
                        # Check if conversation should be summarized and closed
                        should_close = False
                        close_reason = None
                        
                        if reply_data.get('intent_label') in ['not_ready_yet', 'generic_fallback']:
                            should_close = True
                            close_reason = "Customer deferred or conversation concluded"
                        
                        if should_close and TOOLS_AVAILABLE:
                            # Generate and store conversation summary
                            summary = generate_conversation_summary(agreement_id, reply_data.get('intent_label'))
                            upsert_conversation_summary(agreement_id, reply_data.get('intent_label'), summary)
                            
                            # Close the thread
                            close_thread(agreement_number)
                            print(f"   🔒 Thread closed: {close_reason}")
                    else:
                        print(f"   ⚠️  Failed to send reply")
                else:
                    # Out of hours: log deferred message
                    print(f"   ⏰ Out of hours - logging deferred message")
                    if TOOLS_AVAILABLE:
                        log_message(
                            agreement_ref=agreement_number,
                            channel='whatsapp',
                            direction='outbound',
                            message_text=f"[deferred: out of hours] {reply_data.get('reply_text', '')}",
                            intent_label=reply_data.get('intent_label'),
                            template_slug=reply_data.get('template_slug'),
                            source='ai',
                            model=reply_data.get('model')
                        )
            
            except Exception as e:
                print(f"   ❌ Error processing thread {agreement_number}: {e}")
                continue
    
    except Exception as e:
        print(f"❌ Error in process_open_threads: {e}")


def main():
    """Main loop - poll every 60 seconds."""
    print("=" * 60)
    print("  🚀 Android Handler (Rules + Templates)")
    print("=" * 60)
    print(f"  OpenAI: {'✅ Available' if OPENAI_AVAILABLE else '❌ Not available (template-only mode)'}")
    print(f"  Tools:  {'✅ Available' if TOOLS_AVAILABLE else '❌ Limited'}")
    print("=" * 60)
    print("📡 Polling for open threads every 60 seconds...")
    print("Press Ctrl+C to stop\n")
    
    while True:
        try:
            timestamp = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            print(f"\n[{timestamp}] Checking for open threads...")
            
            process_open_threads()
            
            print("⏳ Waiting 60 seconds...\n")
            time.sleep(60)
        
        except KeyboardInterrupt:
            print("\n\n👋 Android Handler stopped")
            break
        
        except Exception as e:
            print(f"❌ Unexpected error: {e}")
            print("⏳ Retrying in 60 seconds...\n")
            time.sleep(60)


if __name__ == '__main__':
    main()
