from datetime import  datetime, timedelta, timezone
from typing import Optional
from fastapi import APIRouter,  Depends, Request
from pydantic import BaseModel
from sqlalchemy.orm import Session
import requests
import os
from requests.auth import HTTPBasicAuth
from app.advisor_service.services.ultravox_service import UltravoxService
from app.common.constants import CallSource, TwilioTerminationReason
from app.common.database import get_db
from app.common.twillio_helper import get_twilio_credentials, get_twillio_client
from app.common.utils import get_current_time_in_timezone
from app.repositories.settings_repo import SettingsRepo
from app.retell_service.routers.retell import get_directory
from app.retell_service.services.caller_info_service import get_caller_name_by_phone_number
from app.utils.helpers.common import get_app_url
from app.common.models import Settings, Advisor, CallsLog, TransferResult
import httpx
from twilio.twiml.voice_response import VoiceResponse
from fastapi import APIRouter, Depends
from fastapi.responses import Response
from twilio.twiml.voice_response import VoiceResponse, Dial

router = APIRouter()

async def start_recording(call_sid: str):
    try:
        account_sid = os.getenv('TWILIO_ACCOUNT_SID')
        auth_token = os.getenv('TWILIO_AUTH_TOKEN')

        url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Calls/{call_sid}/Recordings.json"
        payload = {
            "RecordingStatusCallback": f"{get_app_url()}/advisor/api/v1/twilio/recording-callback",
            "RecordingStatusCallbackEvent": "in-progress completed absent"
        }

        response = requests.post(
            url,
            data=payload,
            auth=HTTPBasicAuth(account_sid, auth_token),
            headers={"Content-Type": "application/x-www-form-urlencoded"}
        )

        data = response.json()
        return {"status": "success", "recording": data}

    except Exception as e:
        return {"status": "error", "message": str(e)}

@router.post("/twilio/recording-callback")
async def recording_callback(request: Request):
    form_data = await request.form()
    print('recording_callback', dict(form_data))
    return Response(content="Recording callback received", media_type="text/plain")

@router.post("/twilio/incoming-webhook")
async def incoming_call(
    request: Request,
     db: Session = Depends(get_db),
):
    try:
        form_data = await request.form()
        twilio_data = dict(form_data)
        twilio_call_id = twilio_data.get("CallSid")
        customer_number = twilio_data.get("Caller")
       
        print('twilio_data', twilio_data)
        directory_response = await get_directory(db)

        print('directory_response', directory_response)

        ULTRAVOX_API_KEY = 'p44BQKIv.S22V6VmcBwGwvkvcHANXTnqXixTpStn6'
        ULTRAVOX_API_URL = "https://api.ultravox.ai/api/calls"
        SYSTEM_PROMPT = """
You are Maya, a helpful, friendly receptionist from Surf City Nissan. Your role is to assist callers with service updates, advisor transfers, and scheduling inquiries.

## IDENTITY
- You are Maya from Surf City Nissan dealership.
- You speak with a pleasant and professional tone, and care deeply about helping the caller.
- Greet every caller by saying:
  "Hi. This is Maya from Surf City Nissan. How may I help you?"
- If the caller asks "Is this Surf City Nissan?" or says a variation like "Serf City Nissan", confidently respond:
  "Yes, you have reached Surf City Nissan! How can I assist you today?"

## GENERAL BEHAVIOR
- Always respond in the **same language** as the caller.
- Do not whisper; maintain a confident, professional tone.
- Stay in character at all times — gently guide the conversation back if it goes off-topic.

## TASKS

1. Always ask for the caller's **name** politely and professionally.

2. If the caller wants to speak with an advisor:
   - First, ask for the name of the caller.
   - Use `ADVISOR_LIST` to check if the requested advisor exists:
     - If not listed: say, "I'm sorry, we don't have anyone by that name."
     - If unavailable: say, "That advisor is currently unavailable."
   - If valid and available: call the `transferCall` function with required parameters.

3. If the user wants to schedule a **visit**, **meeting**, or **test drive**, transfer the call to BDC.
   - Say: "I will transfer you to the scheduling department to assist you further."

4. If the caller wants to **record a message for an advisor**, check the current office hours:
   - Only allow message recording **after office hours**.
   - If it's after hours: ask for the advisor's name and call the `recordMessage` tool.
   - If it's **not** after hours: respond, "The message recording option is only available after office hours have ended."

5. When the caller naturally wraps up (e.g., says "thank you", "bye"):
   - Use the `hangUp` tool to end the call.

6. Do **not** ask the caller to leave a message unless it's after hours and they requested it.

## SPEECH STYLE GUIDELINES

- Do not list service items using numbers (e.g., "first", "second"); explain them in natural, conversational language.
- When giving an address, say the numbers **individually** for a natural sound (e.g., "1 7 3 3 1" instead of "seventeen thousand three hundred thirty-one").

## ADVISOR_LIST:
""" + str(directory_response)
        
        timezone_setting = db.query(Settings).filter(
            Settings.name == 'TIMEZONE_OFFSET').first()
        timezone_offset = int(timezone_setting.value)
        current_time = get_current_time_in_timezone(timezone_offset)
        settings_repo = SettingsRepo(db)
        is_company_online = settings_repo.is_company_online()
        if is_company_online == False:
            SYSTEM_PROMPT = """
- Bot will say:  
  "Our office hours have ended today and no staff member is available to take your call."
- Bot cannot tranfer to any advisors.
- Bot will check if the user wants to leave a message for an advisor:
  - If yes, ask for the advisor's name.
  - Then say something like:  
    "Sure, please tell me your name and the message you'd like me deliver."
  - Wait and Listen for the user's response, which should include both their name and the message.
  - Once both are received, call the `recordMessage` tool with:
    - advisor_name: [name of advisor provided by the user]
    - customer_name: [customer's name from the same response]
    - message: [message content from the same response]

- Bot should not directly ask to end the call but when the bot feels that the call naturally wraps up and the user says end-call greetings (e.g., "thank you", "goodbye"), use the `hangUp` tool to end the call.
"""
        transfer_call_tool = {
            "temporaryTool": {
                "modelToolName": "transferCall",
                "description": "Transfers call to the advisor. Use this to transfer the call.",
                "automaticParameters": [
                    {
                        "name": "callId",
                        "location": "PARAMETER_LOCATION_BODY",
                        "knownValue": "KNOWN_PARAM_CALL_ID"
                    }
                ],
                "dynamicParameters": [
                    {
                        "name": "advisorName",
                        "location": "PARAMETER_LOCATION_BODY",
                        "schema": {
                            "description": "The advisors name",
                            "type": "string"
                        },
                        "required": True
                    },
                    {
                        "name": "advisorPhoneNumber",
                        "location": "PARAMETER_LOCATION_BODY",
                        "schema": {
                            "description": "The advisors phone number",
                            "type": "string"
                        },
                        "required": True
                    }
                ],
                "http": {
                    "baseUrlPattern": f"{get_app_url()}/advisor/api/v1/twilio/transfer-call",
                    "httpMethod": "POST"
                }
            }
        }

        record_message_tool = {
            "temporaryTool": {
                "modelToolName": "recordMessage",
                "description": "Records message for advisor. Use this to record the message.",
                  "automaticParameters": [
                    {
                        "name": "callId",
                        "location": "PARAMETER_LOCATION_BODY",
                        "knownValue": "KNOWN_PARAM_CALL_ID"
                    }
                ],
                "dynamicParameters": [
                    {
                        "name": "advisor_name",
                        "location": "PARAMETER_LOCATION_BODY",
                        "schema": {
                            "description": "The advisors name",
                            "type": "string"
                        },
                        "required": True
                    },
                    {
                        "name": "customer_name",
                        "location": "PARAMETER_LOCATION_BODY",
                        "schema": {
                            "description": "The customer's name who is recording the message.",
                            "type": "string"
                        },
                        "required": True
                    },
                    {
                        "name": "message",
                        "location": "PARAMETER_LOCATION_BODY",
                        "schema": {
                            "description": "The advisors name",
                            "type": "string"
                        },
                        "required": True
                    },
                   
                ],
                "http": {
                    "baseUrlPattern": f"{get_app_url()}/advisor/api/v1/ultravox/record-message",
                    "httpMethod": "POST"
                }
            }
        }

        tools = [
            transfer_call_tool,
            { "toolName": "hangUp" }
        ]

        tools.append(record_message_tool) if not is_company_online else None

        ULTRAVOX_CALL_CONFIG = {
        "systemPrompt": SYSTEM_PROMPT,
        # "model": "fixie-ai/ultravox",
        # "voice": "Riya-Rao-English-Indian",
        "externalVoice": { 
            "elevenLabs": {
               "voiceId": "g6xIsTj2HwM6VR4iXFCw"
            }
        },
        "temperature": 0.3,
        "firstSpeaker": "FIRST_SPEAKER_AGENT",
        "medium": { "twilio": {} },
        "selectedTools": tools
        }
        ultravox_call = None
        async with httpx.AsyncClient() as client:
            ultravox_response = await client.post(
                ULTRAVOX_API_URL,
                json=ULTRAVOX_CALL_CONFIG,
                headers={"Content-Type": "application/json", "X-API-Key": ULTRAVOX_API_KEY}
            )
            ultravox_response.raise_for_status()
            ultravox_call = ultravox_response.json()

            call_log = CallsLog(
                call_date_time=datetime.now(timezone.utc),
                call_id=ultravox_call.get("callId"),
                twilio_call_id=twilio_data.get("CallSid"),
                caller_number=twilio_data.get("Caller"),
                caller_name=ultravox_call.get("From"),
                end_reason='user-hangup',
                source=CallSource.ULTRAVOX,
                sentiment_score="Neutral",
                recording_url=ultravox_call.get("joinUrl"),
                call_status="completed"
            )
            db.add(call_log)
            db.commit()
            db.refresh(call_log)
            print('ultravox_response', ultravox_call)

        join_url = ultravox_call.get("joinUrl")
        if not join_url:
            raise ValueError("Ultravox did not return a joinUrl")
        
        response = VoiceResponse()
        connect = response.connect(
            action=f'{get_app_url()}/advisor/api/v1/twilio/call-connected',
        )
        connect.stream(
            url=join_url,
            status_callback=f'{get_app_url()}/advisor/api/v1/twilio/stream-events',
            status_callback_method='POST'
        )
        return Response(content=str(response), media_type='text/xml')

    except Exception as e:
        print(f"Error: {e}")
        import traceback
        traceback.print_exc()
        error_response = VoiceResponse()
        error_response.say("Sorry, there was an error connecting your call.")
        return Response(content=str(error_response), media_type="application/xml")

@router.post("/twilio/call-connected")
async def call_connected(request: Request):
    form = await request.form()
    call_sid = form.get('CallSid')
    print('call_connected_sid', call_sid)
    print('call_connected_form', dict(form))
    if call_sid:
        recording_response = await start_recording(call_sid)
        print("Recording response:", recording_response)
    
    response = VoiceResponse()
    return Response(content=str(response), media_type='text/xml')

@router.post("/ultravox/record-message")
async def record_message(request: Request, db: Session = Depends(get_db)):
    data = await request.json()
    advisor_name = data.get('advisor_name')
    message = data.get('message')
    call_id = data.get('callId')
    customer_name = data.get('customer_name')

    print('customer_name', customer_name)
    print('advisor_name', advisor_name)
    print('message', message)
    print('call_id', call_id)

    ultravox_service = UltravoxService(db)
    ultravox_service.create_offline_message(advisor_name=advisor_name, 
                                            message=message, 
                                            customer_name=customer_name, 
                                            call_id=call_id)
    
    return Response(content="Record message received", media_type='text/plain')

@router.post("/twilio/stream-events")
async def stream_events(request: Request):
    form = await request.form()
    call_sid = form.get('CallSid')
    stream_event = form.get('StreamEvent')
    print('stream_events_sid', call_sid)
    print('stream_events_form', dict(form))
    if stream_event=='stream-started':
        recording_response = await start_recording(call_sid)
        print("Recording response:", recording_response)
    
        # response = VoiceResponse()
        # return Response(content=str(response), media_type='text/xml')
   
    return Response(content="Stream events received", media_type='text/plain')

@router.post("/twilio/status-callback")
async def status_callback(request: Request, db: Session = Depends(get_db)):
    form_data = await request.form()
    status_callback_data = dict(form_data)
    call_status = status_callback_data.get("CallStatus")
    parent_call_sid = status_callback_data.get("ParentCallSid")
    advisor_number = status_callback_data.get("Called")
    customer_number = status_callback_data.get("From")

    call_sid = status_callback_data.get("CallSid")

    print('status_callback', status_callback_data)
    print('status_callback_data.get("CallStatus")', call_status)
    print('status_callback_data.get("ParentCallSid")', status_callback_data.get("ParentCallSid"))
    print('status_callback_data.get("CallSid")', status_callback_data.get("CallSid"))

    call_data = db.query(CallsLog).filter(CallsLog.twilio_call_id == call_sid).first()
    if call_data:
        call_data.call_status = call_status
        if call_status == TwilioTerminationReason.COMPLETED:
            call_data.end_reason = 'user-hangup'
            call_data.call_duration = timedelta(seconds=int(status_callback_data.get("CallDuration"))) # also updating in /twilio/call-status-callback
        db.commit()
        db.refresh(call_data)

    existing_transfer_result = db.query(TransferResult).filter(TransferResult.twilio_call_id == parent_call_sid).first()
    if existing_transfer_result:
        existing_transfer_result.status = call_status == TwilioTerminationReason.COMPLETED
        db.commit()
        db.refresh(existing_transfer_result)
    else:
        advisor = db.query(Advisor).filter(Advisor.phone_number == advisor_number).first()
        advisor_id = advisor.id if advisor else None
        customer_name = get_caller_name_by_phone_number(customer_number)

        transfer_result = TransferResult(
            twilio_call_id=parent_call_sid,
            status=call_status == TwilioTerminationReason.COMPLETED,
            transferred_to=advisor_id,
            customer_number=customer_number,
            customer_name=customer_name,
            call_datetime=datetime.now(timezone.utc),
        )
        db.add(transfer_result)
        db.commit()
        db.refresh(transfer_result)
      
    return Response(content="Status callback received", media_type="text/plain")

def transfer_active_call(ultravox_call_id, destination_number, db):
    try:
        print('transfer_active_call', ultravox_call_id, destination_number)
        call_data = db.query(CallsLog).filter(CallsLog.call_id == ultravox_call_id).first()
        if not call_data:
            raise ValueError("Call not found or invalid CallSid")

        twilio_call_id = call_data.twilio_call_id
        client = get_twillio_client()
        response = VoiceResponse()
        # response.record(timeout=0, transcribe=True)
        dial = Dial()
        dial.number(destination_number,
                       status_callback_event='initiated ringing answered completed',
                       status_callback=f'{get_app_url()}/advisor/api/v1/twilio/status-callback',
                        status_callback_method='POST'
                    )
        response.append(dial)

        # Update the active call with new TwiML
        updated_call = client.calls(twilio_call_id).update(twiml=str(response))

        call_data.transferred_to = destination_number
        call_data.transfer_status = 'initiated'
        db.commit()
        db.refresh(call_data)
        return {
            "status": "success",
            "message": "Call transfer initiated",
            "call_details": 'nono'
        }

    except Exception as e:
        print("Error transferring call:", e)
        raise {
            "status": "error",
            "message": "Failed to transfer call",
            "error": str(e)
        }


@router.post("/twilio/transfer-call")
async def transfer_call(
    request: Request,
    db: Session = Depends(get_db),
):
    """
    Handle incoming calls from Ultravox
    """
    print('Received transfer call from Ultravox')
    print('request', await request.json())
    data = await request.json()
    ultravox_call_id = data.get("callId")
    destination_number = data.get("advisorPhoneNumber")
    print('destination number', destination_number)
    transfer_active_call(ultravox_call_id, destination_number, db)
    # transfer_active_call(ultravox_call_id, '+923017357180', db)
    return Response(content="Transfer call received", media_type="text/plain")

@router.post("/ultravox/webhook")
async def ultravox_webhook(
    request: Request,
):
    """
    Handle incoming calls from Ultravox
    """
    print('Received webhook from Ultravox')
    print('request', await request.json())
    return Response(content="Webhook received", media_type="text/plain")

class SwitchPlatformRequest(BaseModel):
    mode: Optional[str] = None
    twilioNumber: Optional[str] = None
    trunkId: Optional[str] = None
    webhook_url: Optional[str] = f'{get_app_url()}/advisor/api/v1/twilio/incoming-webhook'

@router.post("/ultravox/update-mode")
async def switch_plateform(request: SwitchPlatformRequest):
    mode = request.mode
    twilio_number = request.twilioNumber
    trunkId = request.trunkId
    webhook_url = request.webhook_url

    client = get_twillio_client()

    number = client.incoming_phone_numbers \
        .list(phone_number=twilio_number)[0]

    print('number.__dict__:', number.__dict__)
    number_sid = number.sid
    sip_twiml_url = 'https://api.retellai.com/api/twiml' 
    
    if mode == 'studio':
        account_sid, auth_token = get_twilio_credentials()
        selected_url = f'https://webhooks.twilio.com/v1/Accounts/{account_sid}/Flows/FW9aa04a6627116ee4f892854d8e0d8b7a'
    else:
        selected_url = webhook_url if mode == 'webhook' else sip_twiml_url

    phone_number = client.incoming_phone_numbers(number_sid).update(
        voice_url=selected_url,
        voice_method='POST',
        status_callback=f'{get_app_url()}/advisor/api/v1/twilio/call-status-callback',
        status_callback_method='POST'
    )
    if mode == 'webhook' or mode == 'studio':
        # client.trunking.v1.trunks('TKd6365fb0135e178239b7de0e8ff66f5d').phone_numbers('PN13da0000142866a37a1c0741cb13c19f').delete()
        client.trunking.v1.trunks(trunkId).phone_numbers(number_sid).delete()
    else:
        client.trunking.v1.trunks(trunkId).phone_numbers.create(phone_number_sid=number_sid)

    print('phone_number', phone_number)
    print(f"Twilio number updated to use: {mode}")
    print(f"Twilio number updated to use: {selected_url}")
    return {
        "status": "success",
        "message": f"Twilio number updated to use: {mode}"
    }

@router.post("/twilio/call-status-callback")
async def call_status(request: Request, db: Session = Depends(get_db)):
    """
    Handle call status callbacks from Twilio
    """
    try:
        form_data = await request.form()
        status_data = dict(form_data)
        
        print("Call Status Callback:", status_data)
        
        call_sid = status_data.get("CallSid")
        call_status = status_data.get("CallStatus")
        call_duration = status_data.get("CallDuration")
        
        call_log = db.query(CallsLog).filter(CallsLog.twilio_call_id == call_sid).first()
        if call_log:
            call_log.call_status = call_status
            if call_duration:
                call_log.call_duration = timedelta(seconds=int(call_duration))
            db.commit()
            db.refresh(call_log)
        
        return Response(content="Status callback received", media_type="text/plain")
        
    except Exception as e:
        print(f"Error processing call status: {str(e)}")
        return Response(content="Error processing status callback", media_type="text/plain")