#!/usr/bin/env python3
# FONIO Voice Bridge - Complete Implementation
# Voice-to-Text Bridge with Turn Detection, LM Studio API, CyODE Integration

import pyaudio
import whisper
import requests
import json
import time
import threading
import wave
import numpy as np
from collections import deque

# Configuration
LM_STUDIO_API = "http://100.64.45.99:1234/v1/chat/completions"  # Win11 Tailscale
LM_STUDIO_KEY = "sk-lm-JNq2Wx4f:AaNmfhRBaHOSRP5xhWus"
MODEL_NAME = "large-v3"  # Whisper-Modell
LANG = "de"

# CyODE Integration
CYODE_API_BASE = "https://cyode.raumkommander.at/api"  # Placeholder - needs real endpoint
INTEGRATION_ID = "e814f8c21622"

# Audio Setup
p = pyaudio.PyAudio()
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024

# Turn Detection Config
SILENCE_THRESHOLD_MS = 500
MIN_SPEECH_DURATION_MS = 200
MAX_TURN_DURATION_MS = 30000

# Audio Buffer for Turn Detection
audio_buffer = deque(maxlen=RATE * MAX_TURN_DURATION_MS // 1000)
context_history = deque(maxlen=10)  # Last 10 turns for context

# State
bridge_running = False
current_turn_audio = []
last_speech_time = 0
last_silence_time = 0

print("FONIO Voice Bridge Active (Turn Detection Logic)")
print(f"Whisper Model: {MODEL_NAME}")
print(f"Language: {LANG}")
print(f"LM Studio API: {LM_STUDIO_API}")

# Whisper Model Load
model = whisper.load_model(MODEL_NAME)

# VAD (Voice Activity Detection) - Simple Energy-based
def detect_speech(data):
    """Simple VAD based on audio energy"""
    audio_array = np.frombuffer(data, dtype=np.int16)
    energy = np.mean(audio_array ** 2)
    # Threshold: > 1000 = speech, < 1000 = silence
    return energy > 1000

def save_turn_as_wav(filename):
    """Save audio buffer as WAV file"""
    with wave.open(filename, 'wb') as wav_file:
        wav_file.setnchannels(CHANNELS)
        wav_file.setsampwidth(p.get_sample_size(FORMAT))
        wav_file.setframerate(RATE)
        for chunk in current_turn_audio:
            wav_file.writeframes(chunk)

def process_voice():
    """Main voice processing loop with turn detection"""
    global bridge_running, current_turn_audio, last_speech_time, last_silence_time

    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)

    print("Audio stream open. Listening...")

    while bridge_running:
        data = stream.read(CHUNK)
        current_time = time.time()

        # VAD check
        is_speech = detect_speech(data)

        if is_speech:
            last_speech_time = current_time
            current_turn_audio.append(data)
            # Reset silence timer when speech detected
            last_silence_time = 0
        else:
            if last_silence_time == 0:
                last_silence_time = current_time

            # Check if silence threshold reached
            if last_silence_time > 0 and (current_time - last_silence_time) * 1000 >= SILENCE_THRESHOLD_MS:
                # Check minimum speech duration
                speech_duration = (last_speech_time - (last_speech_time - (current_time - last_silence_time))) * 1000
                if len(current_turn_audio) > 0 and speech_duration >= MIN_SPEECH_DURATION_MS:
                    # Turn detected - process it
                    process_turn()
                    current_turn_audio = []
                    last_speech_time = 0
                    last_silence_time = 0

    stream.stop_stream()
    stream.close()
    p.terminate()
    print("Bridge Terminated")

def process_turn():
    """Process a detected voice turn"""
    if not current_turn_audio:
        return

    # Save as temporary WAV
    temp_wav = f"/tmp/fonio-turn-{int(time.time())}.wav"
    save_turn_as_wav(temp_wav)

    # Transcribe with Whisper
    print(f"[Transcribe] Processing turn ({len(current_turn_audio)} chunks)")
    try:
        result = model.transcribe(temp_wav, language=LANG, fp16=False)
        text = result["text"].strip()

        if text:
            print(f"[Transcript] {text}")

            # Add to context history
            context_history.append({"role": "user", "content": text})

            # Send to LM Studio with context
            messages = list(context_history)
            payload = {
                "model": "qwen/qwen3.6-35b-a3b",
                "messages": messages,
                "temperature": 0.7
            }

            headers = {"Authorization": f"Bearer {LM_STUDIO_KEY}", "Content-Type": "application/json"}
            response = requests.post(LM_STUDIO_API, json=payload, headers=headers, timeout=30)
            if response.status_code == 200:
                ai_response = response.json()["choices"][0]["message"]["content"]
                print(f"[AI Response] {ai_response}")

                # Add AI response to context
                context_history.append({"role": "assistant", "content": ai_response})

                # Push to CyODE
                push_to_cyode(text, ai_response)
            else:
                print(f"[Error] LM Studio API returned {response.status_code}")
    except Exception as e:
        print(f"[Error] Transcription failed: {e}")

    # Cleanup temp file
    try:
        import os
        os.remove(temp_wav)
    except:
        pass

def push_to_cyode(transcript, response):
    """Push interaction to CyODE integration"""
    payload = {
        "transcript": transcript,
        "response": response,
        "source": "odysseus-voice-bridge",
        "timestamp": time.time()
    }

    try:
        # Placeholder - needs real CyODE API endpoint
        # response = requests.post(f"{CYODE_API_BASE}/integrations/{INTEGRATION_ID}/interactions", json=payload)
        print(f"[CyODE] Would push: {json.dumps(payload, ensure_ascii=False)}")
    except Exception as e:
        print(f"[Error] CyODE push failed: {e}")

# Start Bridge
def start_bridge():
    global bridge_running
    bridge_running = True
    process_voice()

# Stop Bridge
def stop_bridge():
    global bridge_running
    bridge_running = False

if __name__ == '__main__':
    try:
        start_bridge()
    except KeyboardInterrupt:
        stop_bridge()
        print("Bridge terminated by user")
