#!/usr/bin/env python3
"""
Telnyx Assistant System Prompt Patcher
Patches AIVA Voice Assistant with Command Bridge Protocol
"""

import logging
import os
import sys
import time
from typing import Optional, Dict, Any, Tuple
from datetime import datetime
import requests
import psycopg2
from psycopg2.extras import RealDictCursor
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from core.secrets_loader import get_postgres_config, get_telnyx_api_key

# Configuration
TELNYX_API_KEY = get_telnyx_api_key()
ASSISTANT_ID = "assistant-696799a5-e994-4ac1-8f26-7b0923aee682"
TELNYX_BASE_URL = "https://api.telnyx.com/v2"

# The system prompt addition to append
SYSTEM_PROMPT_ADDITION = """=== AIVA COMMAND BRIDGE PROTOCOL v1.0 ===

You are AIVA (Artificial Intelligence Voice Assistant), the Genesis Command Bridge interface. Your primary function is to serve as the voice-enabled relay between Kinan and Claude Code.

DIRECTIVE RECOGNITION TRIGGERS:
Listen for these exact phrases or semantic equivalents indicating Kinan intends a directive for Claude Code:
- "tell Claude..."
- "have Claude..."
- "directive for Claude"
- "command for Claude" 
- "task for Claude"
- "Claude needs to..."
- "ask Claude to..."
- "get Claude to..."

When detected, you MUST NOT execute the task yourself. You must use the Command Bridge tools.

MANDATORY CONFIRMATION PROTOCOL:
Before invoking relay_directive_to_claude, you MUST confirm understanding with Kinan:
"I understand. You want me to tell Claude to [concise rephrasing]. I classify this as [priority level] priority. Should I proceed?"

Wait for explicit confirmation ("yes", "send it", "go ahead") before relaying.

PRIORITY CLASSIFICATION SYSTEM:
Analyze utterance urgency and map to integer priorities:

PRIORITY 10 (Critical/Urgent):
Keywords: "urgent", "immediately", "right now", "critical", "ASAP", "emergency", "drop everything", "top priority"
Context: Production outages, security incidents, blocking issues

PRIORITY 7 (High):
Keywords: "important", "soon", "high priority", "today", "as soon as possible" (without ASAP urgency), "needed today"
Context: Time-sensitive tasks, deadlines today

PRIORITY 5 (Default/Normal):
Keywords: None, or "standard", "normal", "regular"
Context: General development tasks, routine maintenance

PRIORITY 3 (Low/Backlog):
Keywords: "when possible", "low priority", "no rush", "whenever", "someday", "eventually", "background task", "nice to have"
Context: Refactoring, documentation updates, exploratory work

TOOL: relay_directive_to_claude
Parameters:
- directive_text: string (required) - The complete, unambiguous task description
- priority: integer (required) - 3, 5, 7, or 10
- context: string (optional) - Relevant conversation context or constraints

After successful relay: "Done. I've sent that to Claude with [priority] priority. Claude will see this on the next poll cycle."

TOOL: check_claude_status
Parameters: None

When Kinan asks status questions like:
- "What is Claude doing?"
- "Where is Claude?"
- "Is Claude busy?"
- "What's Claude working on?"

Invoke check_claude_status, then translate the JSON response to natural speech:

Response Patterns:
- {"status": "idle"} -> "Claude is currently idle and awaiting directives."
- {"status": "processing", "current_task": "X", "progress": "Y"} -> "Claude is working on [X]. Current progress: [Y]."
- {"status": "completed", "last_task": "X", "completed_at": "timestamp"} -> "Claude completed [X] [time] ago."
- {"queue_length": N} -> "There are [N] tasks in Claude's queue."

EDGE CASE PROTOCOLS:

Ambiguous Directives:
If the intent is unclear, ask: "I want to ensure I relay this accurately. Do you mean [interpretation A] or [interpretation B]?"

Multiple Directives:
If Kinan lists multiple tasks: "I detect [N] directives. Shall I send them as separate tasks with individual priorities, or combine them into a single batch?"

Cancellation Requests:
If Kinan says "cancel that", "don't send", "never mind":
- If tool not yet called: "Understood. I will not relay that to Claude."
- If tool already called: "That directive has already been transmitted to Claude. Shall I send a cancellation request?"

Priority Override:
If Kinan explicitly states a priority number ("make this priority 10"), use that exact value regardless of keyword analysis.

Security & Privacy:
Do not log or repeat sensitive credentials. If a directive contains passwords or keys, warn: "This directive contains sensitive data. It will be encrypted in transit but stored in the task queue. Proceed?"

CONVERSATIONAL CONSTRAINTS:
- Refer to the target as "Claude" or "Claude Code", never "the system", "the AI", or "the backend"
- Be concise but warm. Avoid technical jargon unless Kinan uses it first
- Do not reveal API endpoints, database details, or internal architecture
- If Kinan asks "How does this work?", respond: "I'm connected to Claude through the Genesis Command Bridge. I can relay your directives and check status, but the technical details are handled securely in the background."
"""

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('assistant_patch.log')
    ]
)
logger = logging.getLogger(__name__)


class DatabaseLogger:
    """Handles PostgreSQL logging for audit trail"""
    
    def __init__(self):
        self._ensure_schema()
    
    def _get_connection(self):
        """Create database connection with timeout"""
        pg_config = get_postgres_config()
        if not pg_config.is_configured:
            raise psycopg2.Error("PostgreSQL is not configured.")
        
        return psycopg2.connect(
            **pg_config.to_dsn(),
            connect_timeout=10,
            options='-c statement_timeout=30000'
        )
    
    def _ensure_schema(self):
        """Ensure schema and tables exist"""
        try:
            conn = self._get_connection()
            with conn.cursor() as cur:
                cur.execute("""
                    CREATE SCHEMA IF NOT EXISTS genesis_bridge;
                    
                    CREATE TABLE IF NOT EXISTS genesis_bridge.assistant_updates (
                        id SERIAL PRIMARY KEY,
                        assistant_id VARCHAR(100) NOT NULL,
                        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        previous_prompt_length INTEGER,
                        new_prompt_length INTEGER,
                        previous_prompt_hash VARCHAR(64),
                        new_prompt_hash VARCHAR(64),
                        status VARCHAR(50) NOT NULL,
                        error_message TEXT,
                        api_response_code INTEGER
                    );
                    
                    CREATE INDEX IF NOT EXISTS idx_assistant_updates_time 
                    ON genesis_bridge.assistant_updates(updated_at DESC);
                """)
                conn.commit()
            conn.close()
            logger.info("Database schema initialized")
        except psycopg2.Error as e:
            logger.error(f"Schema initialization failed: {e}")
            raise
    
    def log_update(
        self,
        assistant_id: str,
        prev_length: int,
        new_length: int,
        prev_hash: str,
        new_hash: str,
        status: str,
        error: Optional[str] = None,
        response_code: Optional[int] = None
    ) -> None:
        """Log update attempt to database"""
        try:
            conn = self._get_connection()
            with conn.cursor() as cur:
                cur.execute("""
                    INSERT INTO genesis_bridge.assistant_updates 
                    (assistant_id, previous_prompt_length, new_prompt_length, 
                     previous_prompt_hash, new_prompt_hash, status, error_message, api_response_code)
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                """, (assistant_id, prev_length, new_length, prev_hash, new_hash, status, error, response_code))
                conn.commit()
            conn.close()
            logger.info(f"Update logged: {status}")
        except psycopg2.Error as e:
            logger.error(f"Failed to log update: {e}")


class TelnyxAssistantPatcher:
    """Handles Telnyx API interactions with retry logic"""
    
    def __init__(self, api_key: str, assistant_id: str, db_logger: DatabaseLogger):
        self.api_key = api_key
        self.assistant_id = assistant_id
        self.db_logger = db_logger
        self.base_url = TELNYX_BASE_URL
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
        
        # Setup session with retries
        self.session = requests.Session()
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "PATCH"]
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("https://", adapter)
    
    def _hash_prompt(self, prompt: str) -> str:
        """Generate simple hash for prompt comparison"""
        import hashlib
        return hashlib.sha256(prompt.encode()).hexdigest()[:64]
    
    def get_current_assistant(self) -> Tuple[Optional[Dict[str, Any]], Optional[int]]:
        """Fetch current assistant configuration"""
        url = f"{self.base_url}/ai/assistants/{self.assistant_id}"
        
        try:
            logger.info(f"Fetching assistant {self.assistant_id}")
            response = self.session.get(url, headers=self.headers, timeout=30)
            response_code = response.status_code
            
            if response.status_code == 200:
                data = response.json()
                assistant_data = data.get("data", {})
                logger.info("Assistant fetched successfully")
                return assistant_data, response_code
            else:
                logger.error(f"Failed to fetch assistant: {response.status_code} - {response.text}")
                return None, response_code
                
        except requests.RequestException as e:
            logger.error(f"Request failed: {e}")
            return None, None
    
    def patch_assistant(self, new_prompt: str) -> Tuple[bool, Optional[int], Optional[str]]:
        """Update assistant system prompt"""
        url = f"{self.base_url}/ai/assistants/{self.assistant_id}"
        payload = {"system_prompt": new_prompt}
        
        try:
            logger.info(f"Patching assistant {self.assistant_id}")
            response = self.session.patch(
                url, 
                headers=self.headers, 
                json=payload, 
                timeout=30
            )
            response_code = response.status_code
            
            if response.status_code in [200, 201, 204]:
                logger.info("Assistant patched successfully")
                return True, response_code, None
            else:
                error_msg = f"API Error {response.status_code}: {response.text}"
                logger.error(error_msg)
                return False, response_code, error_msg
                
        except requests.RequestException as e:
            error_msg = str(e)
            logger.error(f"Patch request failed: {error_msg}")
            return False, None, error_msg


def main():
    """Main execution flow"""
    logger.info("=== AIVA Command Bridge Protocol Patcher ===")
    logger.info(f"Target Assistant: {ASSISTANT_ID}")
    
    # Initialize components
    try:
        db_logger = DatabaseLogger()
    except Exception as e:
        logger.critical(f"Database initialization failed: {e}")
        sys.exit(1)
    
    patcher = TelnyxAssistantPatcher(TELNYX_API_KEY, ASSISTANT_ID, db_logger)
    
    # Fetch current state
    current_data, fetch_code = patcher.get_current_assistant()
    
    if current_data is None:
        db_logger.log_update(
            ASSISTANT_ID, 0, 0, "", "", "FAILED_FETCH",
            f"HTTP {fetch_code}" if fetch_code else "Network error",
            fetch_code
        )
        logger.critical("Failed to fetch current assistant configuration")
        sys.exit(1)
    
    current_prompt = current_data.get("system_prompt", "")
    current_length = len(current_prompt)
    current_hash = patcher._hash_prompt(current_prompt)
    
    logger.info(f"Current prompt length: {current_length} chars")
    
    # Check if already patched
    if "AIVA COMMAND BRIDGE PROTOCOL" in current_prompt:
        logger.warning("Command Bridge Protocol already present in system prompt")
        db_logger.log_update(
            ASSISTANT_ID, current_length, current_length, 
            current_hash, current_hash, "SKIPPED", "Protocol already present"
        )
        print("SKIPPED: Protocol already installed")
        sys.exit(0)
    
    # Construct new prompt
    new_prompt = current_prompt + "\n\n" + SYSTEM_PROMPT_ADDITION
    new_length = len(new_prompt)
    new_hash = patcher._hash_prompt(new_prompt)
    
    logger.info(f"New prompt length: {new_length} chars")
    logger.info(f"Size increase: {new_length - current_length} chars")
    
    # Confirm before patch (can be bypassed with --force)
    if "--force" not in sys.argv:
        print(f"\nCurrent prompt: {current_length} chars")
        print(f"New prompt: {new_length} chars")
        print(f"Addition: {len(SYSTEM_PROMPT_ADDITION)} chars")
        confirm = input("Proceed with patch? [y/N]: ")
        if confirm.lower() not in ['y', 'yes']:
            logger.info("Patch aborted by user")
            sys.exit(0)
    
    # Execute patch
    success, patch_code, error = patcher.patch_assistant(new_prompt)
    
    # Log result
    status = "SUCCESS" if success else "FAILED_PATCH"
    db_logger.log_update(
        ASSISTANT_ID,
        current_length,
        new_length,
        current_hash,
        new_hash,
        status,
        error,
        patch_code
    )
    
    if success:
        logger.info("=== Patch completed successfully ===")
        print("SUCCESS: AIVA Command Bridge Protocol installed")
        sys.exit(0)
    else:
        logger.critical(f"=== Patch failed: {error} ===")
        print(f"FAILED: {error}")
        sys.exit(1)


if __name__ == "__main__":
    main()