import mysql.connector
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse, Gather
from app import mysql
import logging
from datetime import datetime
import requests
import uuid
from app.config import Config
from app.utils.celery_app import celery
import mysql.connector

logger = logging.getLogger(__name__)

DEFAULT_FREE_CALL_MINUTES = 0


def fetch_call_minutes_limit(cursor, company_id):
    cursor.execute("""
        SELECT p.no_of_mins
        FROM company_package_payments cpp
        JOIN packages p ON cpp.package_id = p.id
        WHERE cpp.company_id = %s
          AND cpp.status = 'succeeded'
          AND (cpp.expires_at IS NULL OR cpp.expires_at > NOW())
        ORDER BY cpp.completed_at DESC
        LIMIT 1
    """, (company_id,))
    row = cursor.fetchone()
    if not row:
        return DEFAULT_FREE_CALL_MINUTES
    try:
        limit = int(row[0]) if row[0] is not None else None
    except (TypeError, ValueError):
        limit = None
    if limit is None:
        return None
    return max(0, limit)


def fetch_used_call_seconds(cursor, company_id):
    cursor.execute("""
        SELECT COALESCE(SUM(cc.duration_seconds), 0)
        FROM campaign_calls cc
        JOIN call_campaigns c ON cc.campaign_id = c.id
        WHERE c.company_id = %s
          AND cc.duration_seconds IS NOT NULL
    """, (company_id,))
    row = cursor.fetchone()
    return row[0] or 0


def mark_calls_limit_reached(cursor, db_conn, campaign_id):
    cursor.execute("""
        UPDATE campaign_calls
        SET status = 'limit_reached',
            error_message = 'Call minutes limit exceeded'
        WHERE campaign_id = %s AND status = 'pending'
    """, (campaign_id,))
    db_conn.commit()


def create_campaign(company_id, name, script, contacts, retry_attempts=1, retry_delay=30):
    """Create a new call campaign"""
    cursor = mysql.connection.cursor()
    try:
        # Create campaign
        campaign_id = str(uuid.uuid4())
        cursor.execute("""
            INSERT INTO call_campaigns 
            (id, company_id, name, script, retry_attempts, retry_delay)
            VALUES (%s, %s, %s, %s, %s, %s)
        """, (campaign_id, company_id, name, script, retry_attempts, retry_delay))
        
        # Add contacts to campaign
        for contact_id in contacts:
            cursor.execute("""
                INSERT INTO campaign_calls 
                (id, campaign_id, contact_id)
                VALUES (%s, %s, %s)
            """, (str(uuid.uuid4()), campaign_id, contact_id))
        
        mysql.connection.commit()
        return campaign_id
    except Exception as e:
        mysql.connection.rollback()
        logger.error(f"Error creating campaign: {str(e)}")
        raise
    finally:
        cursor.close()
 
# @celery.task
# def process_campaign(campaign_id):
#     """Process a call campaign"""
#     cursor = mysql.connection.cursor()
#     try:
#         logger.info(f"Starting campaign processing for ID: {campaign_id}")
        
#         # Get campaign and settings
#         cursor.execute("""
#             SELECT c.*, cs.twilio_account_sid, cs.twilio_auth_token, cs.twilio_phone_number
#             FROM call_campaigns c
#             JOIN company_settings cs ON c.company_id = cs.company_id
#             WHERE c.id = %s
#         """, (campaign_id,))
#         campaign = cursor.fetchone()
        
#         if not campaign:
#             logger.error(f"Campaign {campaign_id} not found")
#             return
            
#         # Log the credentials being used (mask the auth token for security)
#         account_sid = campaign[9]  
#         auth_token = campaign[10]  
#         phone_number = campaign[11]  
        
#         logger.info(f"Using Twilio Account SID: {account_sid}")
#         logger.info(f"Using Twilio Phone Number: {phone_number}")
        
#         # Initialize Twilio client
#         client = Client(account_sid, auth_token)
        
#         # Get pending calls with more detailed logging
#         cursor.execute("""
#             SELECT cc.*, ct.phone_number
#             FROM campaign_calls cc
#             JOIN contacts ct ON cc.contact_id = ct.id
#             WHERE cc.campaign_id = %s AND cc.status = 'pending'
#         """, (campaign_id,))
#         pending_calls = cursor.fetchall()
        
#         logger.info(f"Found {len(pending_calls)} pending calls to process")
        
#         for call in pending_calls:
#             try:
#                 # Log the call details
#                 logger.info(f"Processing call: {call}")
#                 to_number = call[13]  
#                 logger.info(f"Phone number to call: {to_number}")
                
#                 if not to_number:
#                     logger.error("No phone number found for contact")
#                     continue
                
#                 # Make the call with enhanced parameters including recording
#                 twilio_call = client.calls.create(
#                     to=to_number,
#                     from_=phone_number,
#                     url=f"{Config.BASE_URL}/company/call-campaign/webhook",
#                     status_callback=f"{Config.BASE_URL}/company/call-campaign/status",
#                     status_callback_event=['initiated', 'ringing', 'answered', 'completed'],
#                     status_callback_method='POST',
#                     machine_detection='Enable',
#                     record=True,  # Enable recording for the entire call
#                     recording_status_callback=f"{Config.BASE_URL}/company/call-campaign/recording-complete",
#                     recording_status_callback_method='POST'
#                 )
                
#                 logger.info(f"Call initiated with SID: {twilio_call.sid}")
                
#                 # Update call status
#                 cursor.execute("""
#                     UPDATE campaign_calls
#                     SET call_sid = %s, status = 'in_progress', attempts = attempts + 1
#                     WHERE id = %s
#                 """, (twilio_call.sid, call[0]))
#                 mysql.connection.commit()
                
#             except Exception as e:
#                 logger.error(f"Error making call: {str(e)}")
#                 cursor.execute("""
#                     UPDATE campaign_calls
#                     SET status = 'failed', error_message = %s
#                     WHERE id = %s
#                 """, (str(e), call[0]))
#                 mysql.connection.commit()
                
#     except Exception as e:
#         logger.error(f"Error processing campaign: {str(e)}")
#     finally:
#         cursor.close()

VAPI_API_KEY = "c9988b99-66a9-416f-a994-af674238826d"
VAPI_ASSISTANT_ID = "4e29c224-4696-499f-8563-e8ae2270ef5a"
VAPI_PHONE_NUMBER_ID = "3215f5b3-00d1-40c3-889a-f903717e6418"
VAPI_API_URL = "https://api.vapi.ai/call"

VAPI_API_BASE = "https://api.vapi.ai"

def _vapi_headers():
    return {"Authorization": f"Bearer {VAPI_API_KEY}", "Content-Type": "application/json"}

def get_assistant(assistant_id):
    r = requests.get(f"{VAPI_API_BASE}/assistant/{assistant_id}", headers=_vapi_headers())
    return r.json() if r.ok else {}

def patch_assistant(assistant_id, payload):
    return requests.patch(f"{VAPI_API_BASE}/assistant/{assistant_id}", headers=_vapi_headers(), json=payload)

def apply_campaign_script(assistant_id, campaign_script, opening_line=None):
    """
    Inject the campaign script into model.messages[0] (system) in an idempotent way.
    - Preserves all existing model fields (provider, model, etc.)
    - Removes any previous CAMPAIGN SCRIPT blocks before adding the new one
    """
    base = get_assistant(assistant_id)
    model = (base.get("model") or {}).copy()
    messages = (model.get("messages") or []).copy()

    prev = {
        "messages": messages.copy(),
        "firstMessage": base.get("firstMessage"),
        "model": model.copy()
    }

    def strip_campaign_blocks(text: str) -> str:
        # Remove any previously appended CAMPAIGN SCRIPT sections
        marker = "\n---\nCAMPAIGN SCRIPT:"
        if "CAMPAIGN SCRIPT:" in text:
            # keep only content before the first marker
            head = text.split(marker, 1)[0].rstrip()
            return head
        return text

    if messages and messages[0].get("role") == "system":
        base_system = messages[0].get("content", "") or ""
        base_system = strip_campaign_blocks(base_system)
        messages[0]["content"] = f"{base_system}\n\n---\nCAMPAIGN SCRIPT:\n{campaign_script}\n"
    else:
        # No system message present; create one containing just the campaign script
        messages = [{"role": "system", "content": f"CAMPAIGN SCRIPT:\n{campaign_script}\n"}] + messages

    patched_model = {**model, "messages": messages}
    payload = {"model": patched_model}
    if opening_line is not None:
        payload["firstMessage"] = opening_line

    r = patch_assistant(assistant_id, payload)
    try:
        body = r.json()
    except Exception:
        body = r.text
    # logger.info(f"Applied campaign script -> {r.status_code} {body}")
    return prev

def restore_assistant_prompt(assistant_id, previous):
    # Restore entire previous model to avoid schema errors
    payload = {"model": previous.get("model") or {"messages": previous.get("messages", [])}}
    if "firstMessage" in previous:
        payload["firstMessage"] = previous["firstMessage"]
    r = patch_assistant(assistant_id, payload)
    try:
        body = r.json()
    except Exception:
        body = r.text
    # logger.info(f"Restored assistant prompt -> {r.status_code} {body}")

def set_elevenlabs_voice_speed(assistant_id, speed=1.1):
    """
    Set the speaking speed for ElevenLabs voice in Vapi assistant.
    
    Args:
        assistant_id: Your Vapi assistant ID
        speed: Speaking speed (0.5 = slow, 1.0 = normal, 1.5 = fast, 2.0 = very fast)
    
    Returns:
        (status_code, response_body, previous_voice_config)
    """
    base = get_assistant(assistant_id)
    prev_voice = (base.get("voice") or {}).copy()
    
    # Build new voice config preserving existing settings
    new_voice = prev_voice.copy()
    new_voice["speed"] = float(speed)
    
    r = patch_assistant(assistant_id, {"voice": new_voice})
    try:
        body = r.json()
    except Exception:
        body = r.text
    
    # logger.info(f"Set ElevenLabs speed to {speed} -> {r.status_code} {body}")
    return r.status_code, body, prev_voice

def restore_assistant_voice(assistant_id, previous_voice):
    """Restore the previous voice configuration"""
    if previous_voice:
        r = patch_assistant(assistant_id, {"voice": previous_voice})
        try:
            body = r.json()
        except Exception:
            body = r.text
        # logger.info(f"Restored voice -> {r.status_code} {body}")
        return r.status_code, body
    return 200, "No previous voice to restore"

@celery.task
def process_campaign(campaign_id):
    """Process a call campaign using Vapi"""
    # Create a new MySQL connection for the Celery task
    db_conn = mysql.connector.connect(
        host=Config.MYSQL_HOST,
        user=Config.MYSQL_USER,
        password=Config.MYSQL_PASSWORD,
        database=Config.MYSQL_DB,
        autocommit=False,
        pool_size=5,
        pool_name="celery_pool"
    )
    cursor = db_conn.cursor()
    
    try:
        logger.info(f"Starting campaign processing for ID: {campaign_id}")

        # Set faster speaking speed for ElevenLabs voice
        speed_status, speed_response, prev_voice = set_elevenlabs_voice_speed(VAPI_ASSISTANT_ID, 1.1)
        if speed_status != 200:
            logger.warning(f"Failed to set voice speed: {speed_response}")

        # Fetch campaign script and apply it
        cursor.execute("""
            SELECT company_id, script
            FROM call_campaigns
            WHERE id = %s
        """, (campaign_id,))
        row = cursor.fetchone()
        if not row:
            logger.error(f"Campaign {campaign_id} not found")
            return
        company_id = row[0]
        campaign_script = row[1] or ""

        # Inject campaign script into assistant prompt
        previous_prompt = apply_campaign_script(VAPI_ASSISTANT_ID, campaign_script)

        call_minutes_limit = fetch_call_minutes_limit(cursor, company_id)
        limit_seconds = None if call_minutes_limit is None else call_minutes_limit * 60

        cursor.execute("""
            SELECT COUNT(*)
            FROM campaign_calls
            WHERE campaign_id = %s AND status = 'pending'
        """, (campaign_id,))
        pending_count = cursor.fetchone()[0] or 0
        logger.info(f"Found {pending_count} pending calls to process")

        while True:
            cursor.execute("""
                SELECT cc.id, ct.phone_number
                FROM campaign_calls cc
                JOIN contacts ct ON cc.contact_id = ct.id
                WHERE cc.campaign_id = %s AND cc.status = 'pending'
                ORDER BY cc.created_at ASC
                LIMIT 1
            """, (campaign_id,))
            call = cursor.fetchone()
            if not call:
                break

            if limit_seconds is not None:
                used_seconds = fetch_used_call_seconds(cursor, company_id)
                remaining_seconds = limit_seconds - used_seconds
                if remaining_seconds <= 0:
                    logger.info(f"Call minutes limit reached for company {company_id}. Marking remaining calls.")
                    mark_calls_limit_reached(cursor, db_conn, campaign_id)
                    break

            try:
                call_id, to_number = call[0], call[1]
                logger.info(f"Phone number to call: {to_number}")

                if not to_number:
                    logger.error("No phone number found for contact")
                    continue

                # Make the call using Vapi API
                headers = {
                    "Authorization": f"Bearer {VAPI_API_KEY}",
                    "Content-Type": "application/json"
                }
                data = {
                    "assistantId": VAPI_ASSISTANT_ID,
                    "phoneNumberId": VAPI_PHONE_NUMBER_ID,
                    "customer": {
                        "number": to_number
                    },
                    "metadata": {
                        "campaign_id": campaign_id,
                        "campaign_call_id": call[0]
                    }
                }
                response = requests.post(VAPI_API_URL, headers=headers, json=data)
                vapi_result = response.json()
                vapi_call_id = vapi_result.get("id")

                # logger.info(f"Vapi call response: {vapi_result}")
                # logger.info(f"Vapi call initiated, Vapi Call ID: {vapi_call_id}")

                # Update call status and store Vapi call ID
                cursor.execute("""
                    UPDATE campaign_calls
                    SET status = 'in_progress', attempts = attempts + 1, call_sid = %s
                    WHERE id = %s
                """, (vapi_call_id, call_id))
                db_conn.commit()

            except Exception as e:
                logger.error(f"Error making call: {str(e)}")
                cursor.execute("""
                    UPDATE campaign_calls
                    SET status = 'failed', error_message = %s
                    WHERE id = %s
                """, (str(e), call_id))
                db_conn.commit()

    except Exception as e:
        logger.error(f"Error processing campaign: {str(e)}")
    finally:
        # Restore original voice speed and prompt
        try:
            restore_assistant_voice(VAPI_ASSISTANT_ID, prev_voice)
            restore_assistant_prompt(VAPI_ASSISTANT_ID, previous_prompt)
        except Exception as re:
            logger.error(f"Failed to restore assistant settings: {str(re)}")
        cursor.close()
        db_conn.close()

def handle_call_webhook(call_sid, call_status, speech_result=None):
    """Handle Twilio webhook callbacks"""
    cursor = mysql.connection.cursor()
    try:
        # Get call details
        cursor.execute("""
            SELECT cc.*, c.script
            FROM campaign_calls cc
            JOIN call_campaigns c ON cc.campaign_id = c.id
            WHERE cc.call_sid = %s
        """, (call_sid,))
        call = cursor.fetchone()
        
        if not call:
            return
        
        # Update call status
        cursor.execute("""
            UPDATE campaign_calls
            SET status = %s
            WHERE call_sid = %s
        """, (call_status, call_sid))
        
        # If call completed, store the response
        if call_status == 'completed' and speech_result:
            cursor.execute("""
                UPDATE campaign_calls
                SET response = %s
                WHERE call_sid = %s
            """, (speech_result, call_sid))
        
        mysql.connection.commit()
        
    except Exception as e:
        logger.error(f"Error handling webhook: {str(e)}")
        raise
    finally:
        cursor.close()
