#!/usr/bin/env python3
"""
LiveTalker HTTPS Voice Server
With proper SSL/TLS support for microphone access
"""

import asyncio
import json
import logging
import time
import base64
import numpy as np
import ssl
import os
from typing import Dict, Any
from pathlib import Path

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI(title="LiveTalker HTTPS Voice Server")

# Enable CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

active_connections: Dict[str, Dict] = {}

def simple_vad(audio_data: np.ndarray, threshold: float = 0.01) -> tuple:
    """Simple energy-based voice activity detection"""
    if len(audio_data) == 0:
        return False, 0.0
    
    # Calculate RMS energy
    rms = np.sqrt(np.mean(audio_data ** 2))
    
    # Simple threshold-based detection
    is_speech = rms > threshold
    confidence = min(rms / threshold, 1.0) if threshold > 0 else 0.0
    
    return is_speech, confidence

def create_self_signed_cert():
    """Create a self-signed certificate for HTTPS"""
    try:
        from cryptography import x509
        from cryptography.x509.oid import NameOID
        from cryptography.hazmat.primitives import hashes
        from cryptography.hazmat.primitives.asymmetric import rsa
        from cryptography.hazmat.primitives import serialization
        import datetime

        # Generate private key
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
        )

        # Create certificate
        subject = issuer = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "CA"),
            x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "LiveTalker"),
            x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
        ])

        cert = x509.CertificateBuilder().subject_name(
            subject
        ).issuer_name(
            issuer
        ).public_key(
            private_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            datetime.datetime.utcnow()
        ).not_valid_after(
            datetime.datetime.utcnow() + datetime.timedelta(days=365)
        ).add_extension(
            x509.SubjectAlternativeName([
                x509.DNSName("localhost"),
                x509.DNSName("*.localhost"),
                x509.IPAddress("127.0.0.1"),
                x509.IPAddress("100.118.75.128"),
            ]),
            critical=False,
        ).sign(private_key, hashes.SHA256())

        # Write certificate and key
        cert_path = "server.crt"
        key_path = "server.key"
        
        with open(cert_path, "wb") as f:
            f.write(cert.public_bytes(serialization.Encoding.PEM))
        
        with open(key_path, "wb") as f:
            f.write(private_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.PKCS8,
                encryption_algorithm=serialization.NoEncryption()
            ))
        
        return cert_path, key_path
    
    except ImportError:
        logger.warning("cryptography package not available, using HTTP only")
        return None, None

@app.get("/")
async def root():
    """Main interface with browser compatibility checks"""
    html_content = """
<!DOCTYPE html>
<html>
<head>
    <title>LiveTalker - Voice Assistant</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        * { box-sizing: border-box; }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 
            margin: 0; padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            min-height: 100vh;
        }
        .container { 
            max-width: 1000px; 
            margin: 0 auto; 
            background: rgba(255,255,255,0.1);
            padding: 30px;
            border-radius: 20px;
            backdrop-filter: blur(15px);
            box-shadow: 0 8px 32px rgba(0,0,0,0.2);
        }
        h1 { 
            text-align: center; 
            margin-bottom: 30px;
            font-size: 2.5em;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
        }
        .status-card { 
            padding: 20px; 
            margin: 20px 0; 
            border-radius: 12px; 
            background: rgba(255,255,255,0.15);
            border: 2px solid transparent;
            transition: all 0.3s ease;
        }
        .status-card.active { border-color: #4CAF50; background: rgba(76,175,80,0.25); }
        .status-card.listening { border-color: #FF9800; background: rgba(255,152,0,0.25); }
        .status-card.error { border-color: #f44336; background: rgba(244,67,54,0.25); }
        
        .controls { 
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 30px 0;
        }
        
        .btn { 
            padding: 15px 25px; 
            border: none; 
            border-radius: 10px; 
            background: #4CAF50; 
            color: white; 
            cursor: pointer; 
            font-size: 16px;
            font-weight: 600;
            transition: all 0.3s ease;
            text-decoration: none;
            display: inline-block;
            text-align: center;
        }
        .btn:hover { background: #45a049; transform: translateY(-2px); }
        .btn:disabled { background: #666; cursor: not-allowed; transform: none; }
        .btn.danger { background: #f44336; }
        .btn.primary { background: #2196F3; }
        
        .warning {
            background: rgba(255,193,7,0.2);
            border: 2px solid #FFC107;
            padding: 20px;
            border-radius: 10px;
            margin: 20px 0;
        }
        .error {
            background: rgba(244,67,54,0.2);
            border: 2px solid #f44336;
            padding: 20px;
            border-radius: 10px;
            margin: 20px 0;
        }
        .success {
            background: rgba(76,175,80,0.2);
            border: 2px solid #4CAF50;
            padding: 20px;
            border-radius: 10px;
            margin: 20px 0;
        }
        
        .mic-button { 
            background: #f44336; 
            font-size: 24px; 
            padding: 30px;
            border-radius: 50%;
            width: 100px;
            height: 100px;
            margin: 20px auto;
            display: flex;
            align-items: center;
            justify-content: center;
            border: none;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        .mic-button.recording { 
            background: #ff1744; 
            animation: pulse 1s infinite;
            box-shadow: 0 0 30px rgba(255,23,68,0.5);
        }
        
        @keyframes pulse {
            0%, 100% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.8; transform: scale(1.1); }
        }
        
        .vad-display {
            margin: 30px 0;
            padding: 20px;
            background: rgba(0,0,0,0.3);
            border-radius: 12px;
        }
        
        .vad-bar {
            width: 100%;
            height: 40px;
            background: rgba(255,255,255,0.2);
            border-radius: 20px;
            overflow: hidden;
            position: relative;
            margin: 15px 0;
        }
        
        .vad-level {
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #8BC34A, #FFC107, #FF5722);
            width: 0%;
            transition: width 0.1s ease;
            border-radius: 20px;
        }
        
        .conversation {
            background: rgba(0,0,0,0.4);
            border-radius: 15px;
            padding: 20px;
            margin: 20px 0;
            max-height: 300px;
            overflow-y: auto;
            min-height: 150px;
        }
        
        .message {
            margin: 10px 0;
            padding: 10px 15px;
            border-radius: 8px;
            max-width: 85%;
            word-wrap: break-word;
        }
        .message.user {
            background: rgba(33,150,243,0.4);
            margin-left: auto;
            text-align: right;
        }
        .message.assistant {
            background: rgba(76,175,80,0.4);
            margin-right: auto;
        }
        .message.system {
            background: rgba(158,158,158,0.3);
            margin: 10px auto;
            text-align: center;
            font-style: italic;
            max-width: 90%;
        }
        
        .log {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 10px;
            height: 200px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 12px;
            white-space: pre-wrap;
            border: 1px solid rgba(255,255,255,0.1);
        }
        
        .browser-info {
            font-size: 14px;
            color: #ccc;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🎙️ LiveTalker Voice Assistant</h1>
        
        <!-- Browser Compatibility Check -->
        <div id="browserCheck"></div>
        
        <div class="status-card" id="connectionStatus">
            <h3>🔗 Connection Status</h3>
            <div id="statusText">Ready to connect</div>
        </div>
        
        <div class="controls">
            <button class="btn primary" onclick="connectWebSocket()">🔗 Connect</button>
            <button class="btn" onclick="checkMicrophone()" id="micCheckBtn">🔍 Check Microphone</button>
            <button class="btn" onclick="requestMicrophone()" id="micPermBtn">🎤 Enable Microphone</button>
            <button class="btn" onclick="startListening()" id="startBtn" disabled>🎧 Start Listening</button>
            <button class="btn danger" onclick="stopListening()" id="stopBtn" disabled>🛑 Stop</button>
        </div>
        
        <div class="status-card" id="micStatus">
            <h3>🎤 Microphone Status</h3>
            <div id="micStatusText">Not checked</div>
            <div class="browser-info" id="browserInfo"></div>
        </div>
        
        <div class="vad-display">
            <h3>🎵 Voice Activity Detection</h3>
            <div style="text-align: center;">
                <button class="mic-button" id="micButton" onclick="toggleListening()">🎤</button>
            </div>
            <div class="vad-bar">
                <div class="vad-level" id="vadLevel"></div>
            </div>
            <div style="text-align: center;" id="vadStatus">Click microphone to start</div>
        </div>
        
        <div class="conversation" id="conversation">
            <div class="message system">LiveTalker ready! Enable microphone to start voice conversation...</div>
        </div>
        
        <div class="status-card">
            <h3>📊 Activity Log</h3>
            <div id="log" class="log">Initializing...</div>
        </div>
    </div>

    <script>
        let ws = null;
        let mediaStream = null;
        let audioContext = null;
        let processor = null;
        let isRecording = false;
        let connected = false;
        
        // Check browser compatibility on load
        function checkBrowserCompatibility() {
            const browserCheck = document.getElementById('browserCheck');
            const browserInfo = document.getElementById('browserInfo');
            
            let issues = [];
            let info = [];
            
            // Check HTTPS
            if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
                issues.push('HTTPS required for microphone access (except localhost)');
            }
            
            // Check WebRTC support
            if (!navigator.mediaDevices) {
                issues.push('MediaDevices API not supported');
            } else if (!navigator.mediaDevices.getUserMedia) {
                issues.push('getUserMedia not supported');
            }
            
            // Check WebSocket support
            if (!window.WebSocket) {
                issues.push('WebSocket not supported');
            }
            
            // Check Web Audio API
            if (!window.AudioContext && !window.webkitAudioContext) {
                issues.push('Web Audio API not supported');
            }
            
            // Browser info
            info.push(`Protocol: ${location.protocol}`);
            info.push(`Host: ${location.hostname}`);
            info.push(`User Agent: ${navigator.userAgent.substring(0, 50)}...`);
            
            if (issues.length > 0) {
                browserCheck.innerHTML = \`
                    <div class="error">
                        <h3>⚠️ Browser Compatibility Issues</h3>
                        <ul>\${issues.map(issue => \`<li>\${issue}</li>\`).join('')}</ul>
                        <p><strong>Recommendations:</strong></p>
                        <ul>
                            <li>Use Chrome, Firefox, or Safari</li>
                            <li>Access via HTTPS or localhost</li>
                            <li>Enable microphone permissions</li>
                        </ul>
                    </div>
                \`;
            } else {
                browserCheck.innerHTML = \`
                    <div class="success">
                        <h3>✅ Browser Compatible</h3>
                        <p>Your browser supports all required features for voice interaction!</p>
                    </div>
                \`;
            }
            
            browserInfo.innerHTML = info.join('<br>');
        }
        
        function log(message) {
            const logDiv = document.getElementById('log');
            const timestamp = new Date().toLocaleTimeString();
            logDiv.textContent += \`[\${timestamp}] \${message}\\n\`;
            logDiv.scrollTop = logDiv.scrollHeight;
        }
        
        function updateStatus(elementId, message, className = '') {
            const element = document.getElementById(elementId);
            if (elementId === 'connectionStatus') {
                document.getElementById('statusText').textContent = message;
                element.className = 'status-card ' + className;
            } else if (elementId === 'micStatus') {
                document.getElementById('micStatusText').textContent = message;
                element.className = 'status-card ' + className;
            }
        }
        
        function addMessage(type, content) {
            const conversation = document.getElementById('conversation');
            const message = document.createElement('div');
            message.className = \`message \${type}\`;
            message.textContent = content;
            conversation.appendChild(message);
            conversation.scrollTop = conversation.scrollHeight;
        }
        
        function updateVAD(level, isActive) {
            const vadLevel = document.getElementById('vadLevel');
            const vadStatus = document.getElementById('vadStatus');
            const micButton = document.getElementById('micButton');
            
            vadLevel.style.width = \`\${level * 100}%\`;
            
            if (isActive) {
                vadStatus.textContent = \`🎵 Voice: \${(level * 100).toFixed(0)}%\`;
                micButton.classList.add('recording');
            } else {
                vadStatus.textContent = isRecording ? 
                    \`🔇 Silence: \${(level * 100).toFixed(0)}%\` : 
                    'Click microphone to start';
                micButton.classList.remove('recording');
            }
        }
        
        async function connectWebSocket() {
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsUrl = \`\${protocol}//\${window.location.host}/media-stream\`;
            
            log('Connecting to WebSocket...');
            updateStatus('connectionStatus', '🔄 Connecting...', '');
            
            try {
                ws = new WebSocket(wsUrl);
                
                ws.onopen = function() {
                    connected = true;
                    log('✅ WebSocket connected');
                    updateStatus('connectionStatus', '✅ Connected', 'active');
                };
                
                ws.onmessage = function(event) {
                    try {
                        const data = JSON.parse(event.data);
                        handleServerMessage(data);
                    } catch (e) {
                        log(\`📨 Raw: \${event.data.substring(0, 50)}...\`);
                    }
                };
                
                ws.onclose = function() {
                    connected = false;
                    log('❌ WebSocket disconnected');
                    updateStatus('connectionStatus', '❌ Disconnected', '');
                };
                
                ws.onerror = function(error) {
                    log(\`❌ WebSocket error\`);
                    updateStatus('connectionStatus', '❌ Error', 'error');
                };
                
            } catch (error) {
                log(\`❌ Connection failed: \${error}\`);
                updateStatus('connectionStatus', '❌ Failed', 'error');
            }
        }
        
        function handleServerMessage(data) {
            switch(data.type) {
                case 'config':
                    log('Server ready for voice processing');
                    addMessage('system', 'Connected! Voice processing ready.');
                    break;
                    
                case 'vad_result':
                    updateVAD(data.confidence || 0, data.is_speech || false);
                    break;
                    
                case 'speech_to_text':
                    if (data.text && data.text.trim()) {
                        addMessage('user', data.text);
                        log(\`🗣️ Detected: "\${data.text}"\`);
                    }
                    break;
                    
                case 'ai_response':
                    if (data.text) {
                        addMessage('assistant', data.text);
                        log(\`🤖 AI responded\`);
                    }
                    break;
                    
                case 'conversation_started':
                    addMessage('system', data.message || 'Listening...');
                    updateStatus('connectionStatus', '🎧 Listening...', 'listening');
                    break;
                    
                case 'error':
                    log(\`❌ Server error: \${data.error}\`);
                    addMessage('system', \`Error: \${data.error}\`);
                    break;
            }
        }
        
        function checkMicrophone() {
            log('🔍 Checking microphone availability...');
            
            if (!navigator.mediaDevices) {
                updateStatus('micStatus', '❌ MediaDevices not supported', 'error');
                log('❌ navigator.mediaDevices not available');
                return;
            }
            
            if (!navigator.mediaDevices.getUserMedia) {
                updateStatus('micStatus', '❌ getUserMedia not supported', 'error');
                log('❌ getUserMedia not available');
                return;
            }
            
            updateStatus('micStatus', '✅ Microphone API available', 'success');
            log('✅ Microphone APIs are available');
            document.getElementById('micPermBtn').disabled = false;
        }
        
        async function requestMicrophone() {
            if (!navigator.mediaDevices?.getUserMedia) {
                alert('Microphone not supported by this browser');
                return;
            }
            
            try {
                log('🎤 Requesting microphone permission...');
                updateStatus('micStatus', '🔄 Requesting permission...', '');
                
                mediaStream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                        sampleRate: 16000,
                        channelCount: 1,
                        echoCancellation: true,
                        noiseSuppression: true,
                        autoGainControl: true
                    }
                });
                
                log('✅ Microphone permission granted');
                updateStatus('micStatus', '✅ Microphone ready', 'active');
                
                // Setup audio processing
                const AudioContextClass = window.AudioContext || window.webkitAudioContext;
                audioContext = new AudioContextClass({ sampleRate: 16000 });
                
                const source = audioContext.createMediaStreamSource(mediaStream);
                processor = audioContext.createScriptProcessor(1024, 1, 1);
                
                processor.onaudioprocess = function(event) {
                    if (isRecording && connected) {
                        const inputData = event.inputBuffer.getChannelData(0);
                        sendAudioData(inputData);
                    }
                };
                
                source.connect(processor);
                processor.connect(audioContext.destination);
                
                document.getElementById('startBtn').disabled = false;
                document.getElementById('micPermBtn').disabled = true;
                document.getElementById('micPermBtn').textContent = '✅ Ready';
                
            } catch (error) {
                log(\`❌ Microphone error: \${error.message}\`);
                updateStatus('micStatus', \`❌ \${error.message}\`, 'error');
                
                if (error.name === 'NotAllowedError') {
                    alert('Microphone permission denied. Please allow microphone access and try again.');
                } else if (error.name === 'NotFoundError') {
                    alert('No microphone found. Please check your audio devices.');
                } else {
                    alert(\`Microphone error: \${error.message}\`);
                }
            }
        }
        
        function sendAudioData(audioData) {
            if (!ws || ws.readyState !== WebSocket.OPEN) return;
            
            const int16Array = new Int16Array(audioData.length);
            for (let i = 0; i < audioData.length; i++) {
                int16Array[i] = Math.max(-1, Math.min(1, audioData[i])) * 0x7FFF;
            }
            
            const uint8Array = new Uint8Array(int16Array.buffer);
            const base64String = btoa(String.fromCharCode.apply(null, uint8Array));
            
            ws.send(JSON.stringify({
                type: 'audio',
                data: base64String,
                format: 'pcm_s16le',
                sample_rate: 16000
            }));
        }
        
        function startListening() {
            if (!connected) { alert('Connect WebSocket first'); return; }
            if (!mediaStream) { alert('Enable microphone first'); return; }
            
            isRecording = true;
            log('🎧 Started listening...');
            
            document.getElementById('startBtn').disabled = true;
            document.getElementById('stopBtn').disabled = false;
            
            if (audioContext?.state === 'suspended') {
                audioContext.resume();
            }
            
            ws?.send(JSON.stringify({
                type: 'start_conversation',
                config: { personality: 'luna' }
            }));
        }
        
        function stopListening() {
            isRecording = false;
            log('🛑 Stopped listening');
            updateStatus('connectionStatus', '✅ Connected', 'active');
            updateVAD(0, false);
            
            document.getElementById('startBtn').disabled = false;
            document.getElementById('stopBtn').disabled = true;
            
            ws?.send(JSON.stringify({ type: 'stop_listening' }));
        }
        
        function toggleListening() {
            if (isRecording) {
                stopListening();
            } else {
                startListening();
            }
        }
        
        // Initialize on page load
        document.addEventListener('DOMContentLoaded', function() {
            checkBrowserCompatibility();
            log('LiveTalker Voice Interface loaded');
            log('Next steps: Connect → Check Microphone → Enable → Start Listening');
        });
    </script>
</body>
</html>
    """
    return HTMLResponse(content=html_content)

@app.get("/health")
async def health_check():
    """Health check with HTTPS info"""
    return {
        "status": "healthy",
        "timestamp": time.time(),
        "protocol": "HTTPS" if os.path.exists("server.crt") else "HTTP",
        "features": {
            "microphone_input": True,
            "browser_compatibility": True,
            "https_support": os.path.exists("server.crt"),
            "simple_vad": True
        }
    }

@app.websocket("/media-stream")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket for voice processing"""
    await websocket.accept()
    session_id = f"session_{int(time.time() * 1000)}"
    
    session = {
        "id": session_id,
        "websocket": websocket,
        "audio_buffer": [],
        "conversation": [],
        "is_listening": False
    }
    active_connections[session_id] = session
    
    try:
        await websocket.send_json({
            "type": "config",
            "session_id": session_id,
            "message": "Voice processing ready with browser compatibility"
        })
        
        async for message in websocket.iter_json():
            if message.get("type") == "start_conversation":
                session["is_listening"] = True
                await websocket.send_json({
                    "type": "conversation_started",
                    "message": "Listening for your voice!"
                })
                
            elif message.get("type") == "audio" and session["is_listening"]:
                try:
                    audio_data = base64.b64decode(message["data"])
                    audio_np = np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0
                    
                    if len(audio_np) > 0:
                        is_speech, confidence = simple_vad(audio_np)
                        
                        await websocket.send_json({
                            "type": "vad_result",
                            "is_speech": is_speech,
                            "confidence": confidence,
                            "timestamp": time.time()
                        })
                        
                        if is_speech:
                            session["audio_buffer"].extend(audio_np.tolist())
                            
                            if len(session["audio_buffer"]) > 16000:  # ~1 second
                                duration = len(session["audio_buffer"]) / 16000
                                text = f"Voice detected: {duration:.1f}s speech segment"
                                
                                await websocket.send_json({
                                    "type": "speech_to_text",
                                    "text": text,
                                    "confidence": 0.8
                                })
                                
                                response = f"I heard {duration:.1f} seconds of speech! Your voice is being processed successfully."
                                await websocket.send_json({
                                    "type": "ai_response",
                                    "text": response
                                })
                                
                                session["audio_buffer"] = []
                                
                except Exception as e:
                    await websocket.send_json({
                        "type": "error",
                        "error": f"Audio processing error: {str(e)}"
                    })
                    
            elif message.get("type") == "stop_listening":
                session["is_listening"] = False
                
    except WebSocketDisconnect:
        pass
    except Exception as e:
        logger.error(f"WebSocket error: {e}")
    finally:
        if session_id in active_connections:
            del active_connections[session_id]

if __name__ == "__main__":
    print("🎙️ Starting LiveTalker HTTPS Voice Server...")
    
    # Try to create SSL certificate
    cert_path, key_path = create_self_signed_cert()
    
    ssl_config = None
    if cert_path and key_path:
        ssl_config = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        ssl_config.load_cert_chain(cert_path, key_path)
        print("✅ Created self-signed certificate for HTTPS")
        print("📍 HTTPS URL: https://localhost:8000")
        print("📍 HTTPS URL: https://100.118.75.128:8000")
    else:
        print("⚠️  Running HTTP only - microphone may not work on some browsers")
        print("📍 HTTP URL: http://localhost:8000")
    
    print("")
    print("🔧 Features:")
    print("  ✅ Browser compatibility checking")
    print("  ✅ Microphone permission handling")
    print("  ✅ HTTPS support for modern browsers")
    print("  ✅ Fallback HTTP for localhost")
    print("  ✅ Real voice activity detection")
    print("")
    
    if ssl_config:
        uvicorn.run(
            app,
            host="0.0.0.0",
            port=8000,
            ssl_context=ssl_config,
            log_level="info"
        )
    else:
        uvicorn.run(
            app,
            host="0.0.0.0",
            port=8000,
            log_level="info"
        )