"""
Raspberry Pi Audio Server with Bluetooth Support
Handles audio playback and microphone recording via Bluetooth devices
"""
from fastapi import FastAPI, File, UploadFile, HTTPException, Form
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
import uvicorn
import sounddevice as sd
import soundfile as sf
import numpy as np
import requests
import threading
import time
import tempfile
import os
from datetime import datetime
import logging
import subprocess
import base64
import functools
import sys
from pathlib import Path
from groq import Groq

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scheduler"))
from media_lock import media_lock

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger(__name__)


def exclusive_media(kind: str):
    """Prevent diagnostics from stealing ALSA devices from production."""
    def decorate(func):
        @functools.wraps(func)
        def wrapped(*args, **kwargs):
            with media_lock(kind, timeout=3.0):
                return func(*args, **kwargs)
        return wrapped
    return decorate

@asynccontextmanager
async def lifespan(app: FastAPI):
    """Lifespan event handler for startup and shutdown"""
    global recording_active, groq_client

    # Startup
    logger.info("\n" + "="*60)
    logger.info("🚀 Raspberry Pi Audio Server Starting...")
    logger.info("="*60)
    logger.info("🎧 Bluetooth audio support enabled")
    logger.info("🌐 Server URL: http://0.0.0.0:8000")
    logger.info("📋 API Documentation: http://0.0.0.0:8000/docs")
    logger.info("="*60 + "\n")

    # Initialize Groq client
    if TRANSCRIPTION_ENABLED:
        if GROQ_API_KEY:
            try:
                logger.info("🤖 Initializing Groq Whisper API...")
                logger.info(f"   Model: {WHISPER_MODEL}")
                groq_client = Groq(api_key=GROQ_API_KEY)
                logger.info("✓ Groq API ready\n")
            except Exception as e:
                logger.error(f"✗ Failed to initialize Groq: {e}")
                logger.warning("⚠ Transcription will be disabled\n")
                groq_client = None
        else:
            logger.warning("⚠ GROQ_API_KEY not set")
            logger.warning("⚠ Get free API key from: https://console.groq.com")
            logger.warning("⚠ Transcription will be disabled\n")
            groq_client = None

    # List available audio devices
    try:
        logger.info("🔊 Available audio devices:")
        devices = sd.query_devices()
        for i, device in enumerate(devices):
            logger.info(f"  [{i}] {device['name']} - In: {device['max_input_channels']}, Out: {device['max_output_channels']}")
        logger.info("")
    except Exception as e:
        logger.warning(f"⚠ Could not list audio devices: {e}")

    yield  # Server runs here

    # Shutdown
    recording_active = False
    logger.info("\n✓ Server shutting down")

app = FastAPI(
    title="Raspberry Pi Audio Server",
    description="Audio playback and recording server with Bluetooth support",
    lifespan=lifespan
)

# Configuration
WEBHOOK_AUDIO_DETECTED = os.getenv("WEBHOOK_AUDIO_DETECTED", "")
WEBHOOK_NO_AUDIO = os.getenv("WEBHOOK_NO_AUDIO", "")

# Audio recording settings
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 10
SILENCE_THRESHOLD = 0.00005  # Ultra-sensitive threshold
SILENCE_DURATION = 2.0  # Seconds of silence before stopping
DIAGNOSTIC_MODE = True  # Show audio levels for debugging
MIC_BOOST = 10.0  # Amplification factor for quiet microphones

# Bluetooth audio device settings
BLUETOOTH_DEVICE_NAME = os.getenv("BLUETOOTH_DEVICE_NAME", None)  # e.g., "My Bluetooth Headset"
BLUETOOTH_INPUT_DEVICE = None
BLUETOOTH_OUTPUT_DEVICE = None

# Global state
recording_active = False
audio_workflow_active = False  # Flag to pause motion detection during audio workflow
groq_client = None

# Transcription settings
TRANSCRIPTION_ENABLED = os.getenv("TRANSCRIPTION_ENABLED", "true").lower() == "true"
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "whisper-large-v3")  # whisper-large-v3 or whisper-large-v3-turbo
AUTO_DETECT_LANGUAGE = os.getenv("AUTO_DETECT_LANGUAGE", "true").lower() == "true"

def find_bluetooth_devices():
    """Find Bluetooth audio devices and list all available devices"""
    global BLUETOOTH_INPUT_DEVICE, BLUETOOTH_OUTPUT_DEVICE

    try:
        devices = sd.query_devices()

        logger.info("\n" + "="*60)
        logger.info("🎧 AVAILABLE AUDIO DEVICES:")
        logger.info("="*60)

        for i, device in enumerate(devices):
            device_name = device['name']
            device_name_lower = device_name.lower()

            # Detect device type
            is_bluetooth = any(keyword in device_name_lower for keyword in ['bluetooth', 'bt', 'wireless', 'bluez'])
            is_usb = any(keyword in device_name_lower for keyword in ['usb', 'webcam', 'camera'])

            device_type = ""
            if is_bluetooth:
                device_type = "🎧 BLUETOOTH"
            elif is_usb:
                device_type = "🔌 USB"
            else:
                device_type = "🎵 SYSTEM"

            # Show input/output capabilities
            capabilities = []
            if device['max_input_channels'] > 0:
                capabilities.append(f"IN:{device['max_input_channels']}")
            if device['max_output_channels'] > 0:
                capabilities.append(f"OUT:{device['max_output_channels']}")

            caps_str = " | ".join(capabilities) if capabilities else "NO I/O"

            logger.info(f"  [{i:2d}] {device_type:15s} | {caps_str:12s} | {device_name}")

            # Auto-select Bluetooth devices
            if BLUETOOTH_DEVICE_NAME and BLUETOOTH_DEVICE_NAME.lower() in device_name_lower:
                is_bluetooth = True

            if is_bluetooth:
                if device['max_input_channels'] > 0 and BLUETOOTH_INPUT_DEVICE is None:
                    BLUETOOTH_INPUT_DEVICE = i
                    logger.info(f"       ✓ Selected as Bluetooth INPUT")

                if device['max_output_channels'] > 0 and BLUETOOTH_OUTPUT_DEVICE is None:
                    BLUETOOTH_OUTPUT_DEVICE = i
                    logger.info(f"       ✓ Selected as Bluetooth OUTPUT")

        logger.info("="*60)

        if BLUETOOTH_INPUT_DEVICE is None:
            logger.warning("⚠ No Bluetooth input device found, will use default")
        if BLUETOOTH_OUTPUT_DEVICE is None:
            logger.warning("⚠ No Bluetooth output device found, will use default")

        logger.info("")

    except Exception as e:
        logger.error(f"✗ Error finding Bluetooth devices: {e}")

# Find Bluetooth devices on startup
find_bluetooth_devices()

@exclusive_media("speaker")
def play_audio_file(file_path: str):
    """Play audio file using sounddevice (supports Bluetooth)"""
    try:
        logger.info(f"🔊 Playing audio: {file_path}")

        # Read audio file
        data, samplerate = sf.read(file_path)

        # Play audio on Bluetooth device if available
        device = BLUETOOTH_OUTPUT_DEVICE if BLUETOOTH_OUTPUT_DEVICE is not None else None

        if device is not None:
            logger.info(f"🎧 Using Bluetooth output device: {device}")

        sd.play(data, samplerate, device=device)
        sd.wait()  # Wait until playback is finished

        logger.info(f"✓ Audio playback completed")
        return True
    except Exception as e:
        logger.error(f"✗ Error playing audio: {e}")

        # Fallback to system command
        try:
            logger.info("   Trying aplay fallback...")
            subprocess.run(['aplay', file_path], check=True)
            logger.info(f"✓ Audio playback completed (aplay)")
            return True
        except Exception as e2:
            logger.error(f"✗ Fallback playback also failed: {e2}")
            return False

def detect_audio_level(audio_data, apply_boost=False):
    """Detect if audio level is above threshold"""
    if apply_boost:
        audio_data = audio_data * MIC_BOOST

    volume = np.abs(audio_data).mean()
    max_volume = np.abs(audio_data).max()

    detected = volume > SILENCE_THRESHOLD or max_volume > (SILENCE_THRESHOLD * 3)

    if DIAGNOSTIC_MODE:
        status = "🔴 DETECTED" if detected else "⚪ silence"
        boost_info = f" [BOOSTED x{MIC_BOOST}]" if apply_boost else ""
        logger.info(f"📊 Mean: {volume:.6f}, Max: {max_volume:.6f} | Threshold: {SILENCE_THRESHOLD} | {status}{boost_info}")

    return detected, audio_data if apply_boost else audio_data

def get_available_input_devices():
    """Get list of all available input devices"""
    devices = sd.query_devices()
    input_devices = []
    for i, device in enumerate(devices):
        if device['max_input_channels'] > 0:
            input_devices.append(i)
    return input_devices

@exclusive_media("microphone")
def record_audio_with_vad():
    """
    Record audio from Bluetooth microphone with Voice Activity Detection
    Rotates through available devices if no audio detected
    Returns: (audio_detected: bool, file_path: str or None)
    """
    global recording_active, BLUETOOTH_INPUT_DEVICE

    # Get all available input devices
    available_devices = get_available_input_devices()

    # Try Bluetooth first, then rotate through all devices
    devices_to_try = []
    if BLUETOOTH_INPUT_DEVICE is not None and BLUETOOTH_INPUT_DEVICE in available_devices:
        devices_to_try.append(BLUETOOTH_INPUT_DEVICE)

    # Add other devices
    for dev in available_devices:
        if dev not in devices_to_try:
            devices_to_try.append(dev)

    # If no devices found, try default (None)
    if not devices_to_try:
        devices_to_try = [None]

    for attempt, device in enumerate(devices_to_try):
        frames = []
        audio_detected = False
        silence_start = None
        recording_started = False
        stream = None

        try:
            device_info = sd.query_devices(device) if device is not None else sd.query_devices(kind='input')
            device_name = device_info['name'] if device is not None else "Default"

            logger.info(f"\n{'='*60}")
            logger.info(f"🎤 ATTEMPT {attempt + 1}/{len(devices_to_try)}")
            logger.info(f"{'='*60}")

            if device is not None:
                logger.info(f"🎤 Using device [{device}]: {device_name}")
            else:
                logger.info(f"🎤 Using default input device: {device_name}")

            logger.info(f"Sample rate: {RATE} Hz, Channels: {CHANNELS}")
            logger.info(f"Detection threshold: {SILENCE_THRESHOLD}\n")
            logger.info("🎤 Listening for audio...")

            start_time = time.time()
            chunk_duration = 0.1  # 100ms chunks
            test_duration = 3.0 if attempt > 0 else RECORD_SECONDS  # Quick test for non-primary devices

            while recording_active and (time.time() - start_time) < test_duration:
                # Record a chunk
                chunk = sd.rec(
                    int(chunk_duration * RATE),
                    samplerate=RATE,
                    channels=CHANNELS,
                    dtype='float32',
                    device=device
                )
                sd.wait()

                # Check audio level with boost
                has_audio, boosted_chunk = detect_audio_level(chunk, apply_boost=True)

                if has_audio:
                    if not recording_started:
                        logger.info("🔴 Audio detected! Recording...")
                        recording_started = True
                    audio_detected = True
                    frames.append(boosted_chunk)
                    silence_start = None
                elif recording_started:
                    frames.append(boosted_chunk)
                    if silence_start is None:
                        silence_start = time.time()
                    elif time.time() - silence_start > SILENCE_DURATION:
                        logger.info("⏸ Silence detected, stopping recording")
                        break

            # Save recorded audio if any was detected
            if audio_detected and frames:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                temp_dir = tempfile.gettempdir()
                output_path = os.path.join(temp_dir, f"recorded_audio_{timestamp}.wav")

                # Concatenate all frames
                audio_data = np.concatenate(frames, axis=0)

                # Save using soundfile
                sf.write(output_path, audio_data, RATE)

                logger.info(f"✓ Audio saved: {output_path}")
                logger.info(f"✓ Device [{device}] successfully captured audio!")
                return True, output_path
            else:
                logger.warning(f"⚠ No audio detected on device [{device}]: {device_name}")
                # Continue to next device

        except Exception as e:
            logger.error(f"✗ Error with device [{device}]: {e}")
            # Continue to next device
        finally:
            # Critical: Stop all audio streams and cleanup before trying next device
            try:
                sd.stop()
                time.sleep(0.2)  # Give PortAudio time to cleanup
            except:
                pass

    # If we get here, no device captured audio
    logger.error("✗ No audio detected on any available device")
    recording_active = False
    return False, None

def transcribe_audio(audio_file_path: str):
    """Transcribe audio file using Groq Whisper API"""
    global groq_client

    if not groq_client:
        logger.warning("⚠ Groq client not initialized, skipping transcription")
        return None, None

    try:
        logger.info("\n" + "="*60)
        logger.info("🎙️ Transcribing audio with Groq Whisper API...")
        logger.info("="*60)

        # Open audio file
        with open(audio_file_path, "rb") as audio_file:
            # Call Groq Whisper API
            transcription = groq_client.audio.transcriptions.create(
                file=(os.path.basename(audio_file_path), audio_file.read()),
                model=WHISPER_MODEL,
                response_format="verbose_json",
                language=None if AUTO_DETECT_LANGUAGE else "en"
            )

        transcription_text = transcription.text.strip()
        detected_language = getattr(transcription, 'language', 'unknown')

        if not transcription_text:
            logger.warning("⚠ No speech detected in audio")
            return None, None

        logger.info(f"✓ Detected language: {detected_language}")
        logger.info(f"✓ Transcription: {transcription_text}")
        logger.info("="*60 + "\n")

        return transcription_text, detected_language

    except Exception as e:
        logger.error(f"✗ Transcription error: {e}")
        return None, None

def send_transcription_to_webhook(transcription: str, language: str, audio_file_path: str = None):
    """Send transcription text to webhook"""
    if not WEBHOOK_AUDIO_DETECTED:
        logger.warning("⚠ No webhook URL configured for audio detection")
        return False

    try:
        payload = {
            "transcription": transcription,
            "language": language,
            "timestamp": datetime.now().isoformat(),
            "audio_duration": None
        }

        # Optionally get audio duration
        if audio_file_path and os.path.exists(audio_file_path):
            try:
                data, samplerate = sf.read(audio_file_path)
                duration = len(data) / samplerate
                payload["audio_duration"] = round(duration, 2)
            except:
                pass

        logger.info("\n" + "="*60)
        logger.info("📤 Sending transcription to webhook")
        logger.info("="*60)
        logger.info(f"Text: {transcription}")
        logger.info(f"Language: {language}")

        response = requests.post(
            WEBHOOK_AUDIO_DETECTED,
            json=payload,
            headers={'Content-Type': 'application/json'},
            timeout=30
        )

        if response.status_code == 200:
            logger.info(f"✓ Transcription sent to webhook successfully")
            logger.info("="*60 + "\n")
            return True
        else:
            logger.warning(f"✗ Webhook returned status {response.status_code}")
            logger.info("="*60 + "\n")
            return False
    except Exception as e:
        logger.error(f"✗ Error sending transcription to webhook: {e}")
        logger.info("="*60 + "\n")
        return False

def send_stop_to_webhook():
    """Send stop message to webhook"""
    if not WEBHOOK_NO_AUDIO:
        logger.warning("⚠ No webhook URL configured for no audio")
        return False

    try:
        payload = {
            "status": "stopped",
            "message": "No audio detected",
            "timestamp": datetime.now().isoformat()
        }
        response = requests.post(
            WEBHOOK_NO_AUDIO,
            json=payload,
            headers={'Content-Type': 'application/json'},
            timeout=10
        )

        if response.status_code == 200:
            logger.info(f"✓ Stop message sent to webhook successfully")
            return True
        else:
            logger.warning(f"✗ Webhook returned status {response.status_code}")
            return False
    except Exception as e:
        logger.error(f"✗ Error sending stop to webhook: {e}")
        return False

def audio_workflow(audio_file_path: str):
    """
    Complete audio workflow:
    1. Play audio file via Bluetooth
    2. Start Bluetooth microphone listening
    3. Transcribe recorded audio
    4. Send transcription to webhook
    """
    global recording_active, audio_workflow_active

    # Set flag to pause motion detection
    audio_workflow_active = True

    logger.info(f"\n{'='*60}")
    logger.info(f"🔊 Starting audio workflow")
    logger.info(f"{'='*60}")

    # Step 1: Play the audio file
    success = play_audio_file(audio_file_path)
    if not success:
        logger.warning("⚠ Audio playback had issues, but continuing to recording...")

    time.sleep(0.5)

    # Step 2: Start recording
    logger.info(f"\n{'='*60}")
    logger.info(f"🎤 Starting Bluetooth microphone recording (10s timeout)")
    logger.info(f"{'='*60}")

    recording_active = True
    audio_detected, recorded_file = record_audio_with_vad()

    # Step 3: Transcribe and send to webhook
    if audio_detected and recorded_file:
        logger.info(f"✓ Audio detected")

        # Transcribe the audio
        if TRANSCRIPTION_ENABLED and groq_client:
            transcription, language = transcribe_audio(recorded_file)

            if transcription:
                # Send transcription to webhook
                send_transcription_to_webhook(transcription, language, recorded_file)
            else:
                logger.warning("⚠ Transcription failed - no text to send")
                send_stop_to_webhook()
        else:
            logger.warning("⚠ Transcription disabled - no text to send")
            send_stop_to_webhook()

        # Cleanup temporary file
        try:
            os.remove(recorded_file)
            logger.info(f"🗑 Cleaned up temporary file")
        except:
            pass
    else:
        logger.info(f"⚠ No audio detected - sending stop message")
        send_stop_to_webhook()

    # Cleanup original file
    try:
        os.remove(audio_file_path)
    except:
        pass

    # Clear flag to resume motion detection
    audio_workflow_active = False

    logger.info(f"\n{'='*60}")
    logger.info(f"✓ Workflow completed")
    logger.info(f"{'='*60}\n")

@app.get("/")
async def root():
    """Health check endpoint"""
    return {
        "status": "online",
        "service": "Raspberry Pi Audio Server",
        "bluetooth_input": BLUETOOTH_INPUT_DEVICE,
        "bluetooth_output": BLUETOOTH_OUTPUT_DEVICE,
        "audio_workflow_active": audio_workflow_active,
        "endpoints": {
            "play_audio": "/play-audio (POST)",
            "play_audio_base64": "/play-audio-base64 (POST)",
            "list_devices": "/devices (GET)",
            "set_device": "/set-device (POST)",
            "workflow_status": "/workflow-status (GET)",
            "health": "/ (GET)"
        }
    }

@app.get("/workflow-status")
async def workflow_status():
    """Get current audio workflow status"""
    return {
        "audio_workflow_active": audio_workflow_active,
        "recording_active": recording_active
    }

@app.get("/devices")
async def list_devices():
    """List all available audio devices"""
    try:
        devices = sd.query_devices()
        device_list = []

        for i, device in enumerate(devices):
            device_list.append({
                "index": i,
                "name": device['name'],
                "max_input_channels": device['max_input_channels'],
                "max_output_channels": device['max_output_channels'],
                "default_samplerate": device['default_samplerate']
            })

        return {
            "devices": device_list,
            "bluetooth_input": BLUETOOTH_INPUT_DEVICE,
            "bluetooth_output": BLUETOOTH_OUTPUT_DEVICE
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/set-device")
async def set_device(request: dict):
    """Set Bluetooth input/output devices manually"""
    global BLUETOOTH_INPUT_DEVICE, BLUETOOTH_OUTPUT_DEVICE

    try:
        if "input" in request:
            BLUETOOTH_INPUT_DEVICE = request["input"]
            logger.info(f"✓ Bluetooth input device set to: {BLUETOOTH_INPUT_DEVICE}")

        if "output" in request:
            BLUETOOTH_OUTPUT_DEVICE = request["output"]
            logger.info(f"✓ Bluetooth output device set to: {BLUETOOTH_OUTPUT_DEVICE}")

        return {
            "status": "success",
            "bluetooth_input": BLUETOOTH_INPUT_DEVICE,
            "bluetooth_output": BLUETOOTH_OUTPUT_DEVICE
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/play-audio-base64")
async def play_audio_base64(request: dict):
    """
    Receive base64 encoded audio, play it via Bluetooth, then start Bluetooth mic recording

    Request body (JSON):
    {
        "audio": "base64_encoded_audio_data",
        "format": "mp3" (optional, default: "wav")
    }
    """
    try:
        audio_base64 = request.get("audio") or request.get("data")
        audio_format = request.get("format", "wav")

        if not audio_base64:
            raise HTTPException(
                status_code=400,
                detail="Missing 'audio' or 'data' field with base64 encoded audio"
            )

        logger.info(f"\n✓ Decoding base64 audio data...")
        audio_bytes = base64.b64decode(audio_base64)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        temp_dir = tempfile.gettempdir()
        temp_path = os.path.join(temp_dir, f"incoming_audio_{timestamp}.{audio_format}")

        with open(temp_path, "wb") as f:
            f.write(audio_bytes)

        logger.info(f"✓ Decoded audio: {len(audio_bytes)} bytes, format: {audio_format}")

        # Start workflow in background thread
        thread = threading.Thread(
            target=audio_workflow,
            args=(temp_path,),
            daemon=True
        )
        thread.start()

        return JSONResponse(
            status_code=200,
            content={
                "status": "success",
                "message": "Audio workflow started (Bluetooth)",
                "size_bytes": len(audio_bytes),
                "format": audio_format
            }
        )

    except base64.binascii.Error:
        raise HTTPException(status_code=400, detail="Invalid base64 encoding")
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"✗ Error processing base64 audio: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/play-audio")
async def play_audio(
    audio: UploadFile = File(None),
    url: str = Form(None)
):
    """
    Receive audio file or URL, play it via Bluetooth, then start Bluetooth mic recording

    Parameters:
    - audio: Audio file upload (optional if url provided)
    - url: URL to audio file (optional if audio provided)
    """
    try:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        temp_dir = tempfile.gettempdir()
        temp_path = os.path.join(temp_dir, f"incoming_audio_{timestamp}.wav")

        if url:
            logger.info(f"\n✓ Downloading audio from URL: {url}")
            response = requests.get(url, timeout=30)
            if response.status_code != 200:
                raise HTTPException(status_code=400, detail=f"Failed to download audio from URL: {response.status_code}")

            with open(temp_path, "wb") as f:
                f.write(response.content)

            filename = url.split('/')[-1] or "audio_from_url"
            size = len(response.content)
            logger.info(f"✓ Downloaded: {filename} ({size} bytes)")

        elif audio:
            logger.info(f"\n✓ Receiving uploaded audio file...")
            content = await audio.read()

            with open(temp_path, "wb") as f:
                f.write(content)

            filename = audio.filename
            size = len(content)
            logger.info(f"✓ Received: {filename} ({size} bytes)")

        else:
            raise HTTPException(
                status_code=400,
                detail="Either 'audio' file or 'url' parameter is required"
            )

        # Start workflow in background thread
        thread = threading.Thread(
            target=audio_workflow,
            args=(temp_path,),
            daemon=True
        )
        thread.start()

        return JSONResponse(
            status_code=200,
            content={
                "status": "success",
                "message": "Audio workflow started (Bluetooth)",
                "filename": filename,
                "size_bytes": size
            }
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"✗ Error processing audio: {e}")
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8000,
        log_level="info"
    )
