#!/usr/bin/env python3
"""
Genesis Voice AI Wrapper Skill
===============================
Manages Voice AI integrations for lead engagement.
Supports VAPI, ElevenLabs, and VoiceAIWrapper platforms.

PM-041: Voice AI Wrapper Skill Enhancement
- Supports: VAPI, ElevenLabs voice integration
- Logs transcripts
- Complete voice agent management
"""

import os
import json
import logging
import requests
from typing import Dict, Any, List, Optional
from datetime import datetime
from dataclasses import dataclass, asdict

logger = logging.getLogger("VoiceWrapperSkill")
logging.basicConfig(level=logging.INFO)


@dataclass
class CallRecord:
    """Represents a voice call record."""
    call_id: str
    campaign_id: str
    contact_id: str
    phone_number: str
    direction: str  # inbound, outbound
    status: str
    duration_seconds: int
    transcript: str
    recording_url: str
    disposition: str
    started_at: str
    ended_at: str
    metadata: Dict[str, Any]

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


class VoiceWrapperSkill:
    """
    Voice AI integration skill supporting multiple providers.
    Manages VAPI, ElevenLabs, and VoiceAIWrapper for voice agent operations.
    """

    def __init__(self, api_key: Optional[str] = None,
                 provider: str = "voiceaiwrapper",
                 vapi_key: Optional[str] = None,
                 elevenlabs_key: Optional[str] = None):
        """
        Initialize voice wrapper skill.

        Args:
            api_key: Primary API key (VoiceAIWrapper)
            provider: Default provider (voiceaiwrapper, vapi, elevenlabs)
            vapi_key: VAPI API key
            elevenlabs_key: ElevenLabs API key
        """
        self.provider = provider

        # API keys
        self.api_keys = {
            "voiceaiwrapper": api_key or os.getenv("VOICEAIWRAPPER_API_KEY"),
            "vapi": vapi_key or os.getenv("VAPI_API_KEY"),
            "elevenlabs": elevenlabs_key or os.getenv("ELEVENLABS_API_KEY")
        }

        # Base URLs
        self.base_urls = {
            "voiceaiwrapper": "https://app.voiceaiwrapper.com/api/v1",
            "vapi": "https://api.vapi.ai",
            "elevenlabs": "https://api.elevenlabs.io/v1"
        }

        # Call records storage
        self.call_records: List[CallRecord] = []
        self.transcripts: Dict[str, str] = {}

        logger.info(f"Voice Wrapper Skill initialized (provider: {provider})")

    def _get_headers(self, provider: str = None) -> Dict[str, str]:
        """Get headers for API requests."""
        provider = provider or self.provider
        api_key = self.api_keys.get(provider)

        if not api_key:
            raise ValueError(f"API key not configured for {provider}")

        if provider == "elevenlabs":
            return {
                "xi-api-key": api_key,
                "Content-Type": "application/json"
            }
        else:
            return {
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json"
            }

    def _make_request(self, method: str, endpoint: str,
                      data: Dict = None, provider: str = None) -> Dict[str, Any]:
        """Make API request to voice provider."""
        provider = provider or self.provider
        base_url = self.base_urls.get(provider)
        url = f"{base_url}{endpoint}"

        try:
            headers = self._get_headers(provider)

            if method.upper() == "GET":
                response = requests.get(url, headers=headers, timeout=30)
            elif method.upper() == "POST":
                response = requests.post(url, headers=headers, json=data, timeout=30)
            elif method.upper() == "DELETE":
                response = requests.delete(url, headers=headers, timeout=30)
            else:
                return {"error": f"Unsupported method: {method}"}

            if response.status_code in [200, 201]:
                return response.json() if response.text else {"status": "success"}
            else:
                logger.error(f"API Error {response.status_code}: {response.text}")
                return {"error": response.text, "status_code": response.status_code}

        except Exception as e:
            logger.error(f"Request failed: {e}")
            return {"error": str(e)}

    # ===== VOICEAIWRAPPER OPERATIONS =====

    def inject_lead_to_campaign(self, campaign_id: str,
                                 lead_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Injects a lead into a VoiceAIWrapper campaign.

        Args:
            campaign_id: Target campaign ID
            lead_data: Lead contact information

        Returns:
            Injection result
        """
        logger.info(f"Injecting lead into campaign {campaign_id}")

        payload = {
            "campaign_id": campaign_id,
            "contacts": [{
                "first_name": lead_data.get("first_name", ""),
                "last_name": lead_data.get("last_name", ""),
                "phone": lead_data.get("phone"),
                "email": lead_data.get("email", ""),
                "custom_variables": lead_data.get("custom_variables", {})
            }]
        }

        return self._make_request("POST", "/campaigns/add-contacts",
                                  data=payload, provider="voiceaiwrapper")

    def fetch_call_logs(self, campaign_id: str = None,
                        limit: int = 100) -> List[Dict[str, Any]]:
        """
        Retrieves call history.

        Args:
            campaign_id: Optional campaign filter
            limit: Max records to fetch

        Returns:
            List of call records
        """
        logger.info(f"Fetching call logs (campaign: {campaign_id})")

        endpoint = "/calls"
        if campaign_id:
            endpoint += f"?campaign_id={campaign_id}&limit={limit}"
        else:
            endpoint += f"?limit={limit}"

        result = self._make_request("GET", endpoint, provider="voiceaiwrapper")
        return result.get("calls", []) if isinstance(result, dict) else []

    # ===== VAPI OPERATIONS =====

    def create_vapi_assistant(self, name: str, system_prompt: str,
                               voice_id: str = None,
                               first_message: str = None) -> Dict[str, Any]:
        """
        Creates a VAPI voice assistant.

        Args:
            name: Assistant name
            system_prompt: System instructions
            voice_id: ElevenLabs voice ID
            first_message: Initial greeting

        Returns:
            Created assistant details
        """
        logger.info(f"Creating VAPI assistant: {name}")

        payload = {
            "name": name,
            "model": {
                "provider": "openai",
                "model": "gpt-4",
                "systemPrompt": system_prompt
            },
            "voice": {
                "provider": "11labs",
                "voiceId": voice_id or "21m00Tcm4TlvDq8ikWAM"  # Default voice
            }
        }

        if first_message:
            payload["firstMessage"] = first_message

        return self._make_request("POST", "/assistant", data=payload, provider="vapi")

    def start_vapi_call(self, assistant_id: str, phone_number: str,
                        metadata: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        Initiates an outbound VAPI call.

        Args:
            assistant_id: VAPI assistant ID
            phone_number: Target phone number
            metadata: Call metadata

        Returns:
            Call initiation result
        """
        logger.info(f"Starting VAPI call to {phone_number}")

        payload = {
            "assistantId": assistant_id,
            "phoneNumber": phone_number,
            "metadata": metadata or {}
        }

        return self._make_request("POST", "/call/phone", data=payload, provider="vapi")

    def get_vapi_call(self, call_id: str) -> Dict[str, Any]:
        """Gets VAPI call details."""
        return self._make_request("GET", f"/call/{call_id}", provider="vapi")

    def list_vapi_assistants(self) -> List[Dict[str, Any]]:
        """Lists all VAPI assistants."""
        result = self._make_request("GET", "/assistant", provider="vapi")
        return result if isinstance(result, list) else result.get("assistants", [])

    # ===== ELEVENLABS OPERATIONS =====

    def list_elevenlabs_voices(self) -> List[Dict[str, Any]]:
        """Lists available ElevenLabs voices."""
        result = self._make_request("GET", "/voices", provider="elevenlabs")
        return result.get("voices", []) if isinstance(result, dict) else []

    def generate_speech(self, text: str, voice_id: str = None,
                        model_id: str = "eleven_monolingual_v1") -> Dict[str, Any]:
        """
        Generates speech audio from text using ElevenLabs.

        Args:
            text: Text to convert to speech
            voice_id: Voice ID (default: Rachel)
            model_id: Model to use

        Returns:
            Audio generation result
        """
        logger.info(f"Generating speech: {text[:50]}...")

        voice_id = voice_id or "21m00Tcm4TlvDq8ikWAM"  # Rachel voice

        payload = {
            "text": text,
            "model_id": model_id,
            "voice_settings": {
                "stability": 0.5,
                "similarity_boost": 0.5
            }
        }

        # Note: This returns audio bytes, not JSON
        return self._make_request("POST", f"/text-to-speech/{voice_id}",
                                  data=payload, provider="elevenlabs")

    def clone_voice(self, name: str, audio_files: List[str],
                    description: str = None) -> Dict[str, Any]:
        """
        Creates a voice clone from audio samples.

        Args:
            name: Voice clone name
            audio_files: List of audio file paths
            description: Voice description

        Returns:
            Voice clone result
        """
        logger.info(f"Cloning voice: {name}")

        # Voice cloning requires multipart form data
        # This is a simplified implementation
        payload = {
            "name": name,
            "description": description or f"Genesis voice clone: {name}"
        }

        return self._make_request("POST", "/voices/add", data=payload, provider="elevenlabs")

    # ===== GHL WEBHOOK INTEGRATION =====

    def handle_ghl_webhook_trigger(self, ghl_payload: Dict[str, Any]) -> Dict[str, Any]:
        """
        Processes an incoming GHL webhook to trigger a voice call.
        Entry point for GHL Round-Trip sequence.

        Args:
            ghl_payload: Webhook payload from GHL

        Returns:
            Processing result
        """
        logger.info("GHL Webhook received -> Triggering voice agent")

        # Map GHL fields to voice lead schema
        lead = {
            "first_name": ghl_payload.get("first_name", "Unknown"),
            "last_name": ghl_payload.get("last_name", ""),
            "phone": ghl_payload.get("phone"),
            "email": ghl_payload.get("email"),
            "custom_variables": {
                "niche": ghl_payload.get("niche", "General"),
                "service_area": ghl_payload.get("service_area", "Global"),
                "ghl_contact_id": ghl_payload.get("contact_id")
            }
        }

        campaign_id = ghl_payload.get("voi_campaign_id")
        if not campaign_id:
            logger.error("Missing voi_campaign_id in GHL payload")
            return {"status": "error", "message": "Missing campaign_id"}

        return self.inject_lead_to_campaign(campaign_id, lead)

    def process_call_outcome(self, outcome_payload: Dict[str, Any]) -> Dict[str, Any]:
        """
        Processes post-call webhook from voice provider.
        Formats data for GHL consumption.

        Args:
            outcome_payload: Call outcome data

        Returns:
            Formatted GHL update payload
        """
        logger.info(f"Processing call outcome: {outcome_payload.get('call_id')}")

        # Store transcript
        call_id = outcome_payload.get("call_id", "")
        transcript = outcome_payload.get("transcript", "")

        if call_id and transcript:
            self.transcripts[call_id] = transcript
            self._log_transcript(call_id, transcript)

        # Create call record
        record = CallRecord(
            call_id=call_id,
            campaign_id=outcome_payload.get("campaign_id", ""),
            contact_id=outcome_payload.get("contact_id", ""),
            phone_number=outcome_payload.get("phone", ""),
            direction=outcome_payload.get("direction", "outbound"),
            status=outcome_payload.get("status", "completed"),
            duration_seconds=outcome_payload.get("duration", 0),
            transcript=transcript,
            recording_url=outcome_payload.get("recording_url", ""),
            disposition=outcome_payload.get("disposition", ""),
            started_at=outcome_payload.get("started_at", ""),
            ended_at=outcome_payload.get("ended_at", datetime.utcnow().isoformat()),
            metadata=outcome_payload.get("metadata", {})
        )

        self.call_records.append(record)

        # Format for GHL update
        ghl_update = {
            "contact_id": record.contact_id,
            "call_disposition": record.disposition,
            "transcript_summary": self._summarize_transcript(transcript),
            "recording_link": record.recording_url,
            "duration_seconds": record.duration_seconds,
            "genesis_gate_status": "VALIDATED"
        }

        return ghl_update

    def _log_transcript(self, call_id: str, transcript: str) -> None:
        """Log transcript to persistent storage."""
        try:
            log_dir = "data/transcripts"
            os.makedirs(log_dir, exist_ok=True)

            log_entry = {
                "call_id": call_id,
                "timestamp": datetime.utcnow().isoformat(),
                "transcript": transcript
            }

            with open(f"{log_dir}/transcripts.jsonl", "a") as f:
                f.write(json.dumps(log_entry) + "\n")

            logger.info(f"Transcript logged: {call_id}")
        except Exception as e:
            logger.error(f"Failed to log transcript: {e}")

    def _summarize_transcript(self, transcript: str, max_length: int = 200) -> str:
        """Create a brief summary of the transcript."""
        if not transcript:
            return ""
        if len(transcript) <= max_length:
            return transcript
        return transcript[:max_length] + "..."

    # ===== ANALYTICS =====

    def get_call_metrics(self) -> Dict[str, Any]:
        """Get call performance metrics."""
        total_calls = len(self.call_records)
        if total_calls == 0:
            return {"total_calls": 0, "avg_duration": 0}

        total_duration = sum(r.duration_seconds for r in self.call_records)

        disposition_counts = {}
        for record in self.call_records:
            disp = record.disposition or "unknown"
            disposition_counts[disp] = disposition_counts.get(disp, 0) + 1

        return {
            "total_calls": total_calls,
            "avg_duration_seconds": total_duration / total_calls,
            "total_duration_minutes": total_duration / 60,
            "by_disposition": disposition_counts,
            "transcripts_logged": len(self.transcripts)
        }

    def get_transcripts(self, call_ids: List[str] = None) -> Dict[str, str]:
        """Get transcripts, optionally filtered by call IDs."""
        if call_ids:
            return {cid: self.transcripts.get(cid, "") for cid in call_ids}
        return self.transcripts

    # ===== GENERIC EXECUTE =====

    def execute(self, action: str, **kwargs) -> Dict[str, Any]:
        """
        Generic action executor for skill interface.

        Args:
            action: Action name
            **kwargs: Action parameters

        Returns:
            Action result
        """
        action_map = {
            # VoiceAIWrapper
            "inject_lead": self.inject_lead_to_campaign,
            "fetch_calls": self.fetch_call_logs,

            # VAPI
            "create_assistant": self.create_vapi_assistant,
            "start_call": self.start_vapi_call,
            "get_call": self.get_vapi_call,
            "list_assistants": self.list_vapi_assistants,

            # ElevenLabs
            "list_voices": self.list_elevenlabs_voices,
            "generate_speech": self.generate_speech,
            "clone_voice": self.clone_voice,

            # Webhooks
            "handle_ghl_webhook": self.handle_ghl_webhook_trigger,
            "process_outcome": self.process_call_outcome,

            # Analytics
            "get_metrics": self.get_call_metrics,
            "get_transcripts": self.get_transcripts
        }

        if action not in action_map:
            return {"error": f"Unknown action: {action}", "available": list(action_map.keys())}

        try:
            return action_map[action](**kwargs)
        except TypeError as e:
            return {"error": f"Invalid parameters for {action}: {e}"}
        except Exception as e:
            return {"error": str(e)}


if __name__ == "__main__":
    # Self-test
    skill = VoiceWrapperSkill()

    print("\n=== Voice Wrapper Skill Test ===")

    # Test GHL webhook handling
    mock_ghl = {
        "first_name": "Mario",
        "last_name": "Plumber",
        "phone": "+61400000000",
        "niche": "Plumbing",
        "voi_campaign_id": "cmp_tradie_001"
    }

    result = skill.handle_ghl_webhook_trigger(mock_ghl)
    print(f"GHL Webhook Result: {result}")

    # Test call outcome processing
    mock_outcome = {
        "call_id": "call_test_123",
        "contact_id": "con_123",
        "phone": "+61400000000",
        "duration": 120,
        "disposition": "interested",
        "transcript": "Hi, this is Mario. I have a blocked drain that needs urgent attention.",
        "recording_url": "https://example.com/recording.mp3"
    }

    ghl_update = skill.process_call_outcome(mock_outcome)
    print(f"GHL Update: {ghl_update}")

    # Get metrics
    print(f"Call Metrics: {skill.get_call_metrics()}")

    # Get transcripts
    print(f"Transcripts: {skill.get_transcripts()}")
