import sounddevice as sd
import numpy as np
import pyttsx3
import google.generativeai as genai
from faster_whisper import WhisperModel
from AppOpener import open as open_app

# --- 1. SETUP & CONFIGURATION ---
GEMINI_API_KEY = "YOUR KEY HERE"
genai.configure(api_key=GEMINI_API_KEY)

gemini_model = genai.GenerativeModel('gemini-3-flash-preview')
#gemini_model = genai.GenerativeModel('gemini-2.5-flash-lite')                                   # We use older model to get more requests.


whisper_model = WhisperModel("small", compute_type="int8")                                      # Whisper for Transcription (we use the small int8 version for faster performance on CPU).

def speak(text):
    print(f"Assistant: {text}")

    temp_engine = pyttsx3.init()                                                                # We use temp engine because on some systems, the audio driver can get locked if we reuse the same engine instance repeatedly without stopping it.
    temp_engine.setProperty('rate', 175)
    
    temp_engine.say(text)
    temp_engine.runAndWait()
        
    temp_engine.stop()                                                                          # Explicitly stop to release the audio device.


# --- 2. THE LOGIC ---

def record_audio_stable(samplerate=16000, threshold=0.01):
    print("Listening...")

    chunk_size = 1024
    recording = []
    silent_chunks = 0
    
    with sd.InputStream(samplerate=samplerate, channels=1, dtype='float32') as stream:
        while True:
            data, overflowed = stream.read(chunk_size)
            recording.append(data)
            rms = np.sqrt(np.mean(data**2))
            if rms < threshold:
                silent_chunks += 1
            else:
                silent_chunks = 0
            
            if silent_chunks > (samplerate / chunk_size * 1.5) or len(recording) > 150:
                break
                
    return np.concatenate(recording, axis=0).flatten()

def transcribe(audio_data):
    """Converts speech to text, restricted to English and Greek."""
                                                                                                # Added the 'initial_prompt' and 'language' constraints
                                                                                                # While Whisper detects language automatically, we guide it here
    segments, info = whisper_model.transcribe(
        audio_data, 
        beam_size=5,
        initial_prompt="User is speaking either English or Greek."
    )
    
    if info.language not in ['en', 'el']:
        print(f"(Detected ignored language: {info.language})")
        return ""

    return " ".join([segment.text for segment in segments]).strip()

def get_gemini_response(user_input):
    """Gets a smart response using streaming to reduce latency."""
    try:
        # 1. Define the system rules once.
        system_rules = (
            "You got given the name Spirouliis AI. "
            "You are a witty and helpful voice assistant. "
            "Speak ONLY English or Greek. Match the user's language. "
            "CRITICAL: Keep answers to two short sentences or less."
        )
        
        # 2. Use stream=True to get chunks of text immediately
        response = gemini_model.generate_content(
            f"{system_rules}\nUser: {user_input}",
            stream=True
        )
        
        full_text = ""
        for chunk in response:
            # You can see the response growing in the terminal
            if chunk.text:
                full_text += chunk.text
        
        # 3. Clean up the text (remove any markdown stars like **text**)
        clean_text = full_text.replace("*", "").strip()
        return clean_text

    except Exception as e:
        print(f"Gemini API Error: {e}")
        return "I'm having trouble connecting to my brain."

# --- 3. MAIN RUN LOOP ---
if __name__ == "__main__":
    speak("System online.")
    
    error_count = 0
    MAX_ERRORS = 2

    while True:
        try:
            audio = record_audio_stable()
            segments, info = whisper_model.transcribe(
                audio, 
                beam_size=5,
                initial_prompt="User is speaking either English or Greek."
            )
            user_text = " ".join([segment.text for segment in segments]).strip()

            # --- ERROR CHECK 1: Silence or too short ---
            if not user_text or len(user_text) < 2:
                error_count += 1
                print(f"No input detected ({error_count}/{MAX_ERRORS})")
                if error_count >= MAX_ERRORS:
                    speak("I haven't heard anything. Closing now.")
                    break
                continue

            # --- ERROR CHECK 2: Language Restriction (Italian, etc.) ---
            if info.language not in ['en', 'el']:
                error_count += 1
                msg = f"Sorry, I can't understand your language. ERROR COUNTER ({error_count}/{MAX_ERRORS})"
                speak(msg)
                
                if error_count >= MAX_ERRORS:
                    speak("Goodbye!")
                    break
                continue

            # --- SUCCESS: Valid input received ---
            error_count = 0                                                                     # Reset counter on successful understanding
            print(f"You: {user_text}")
            
            
            words = user_text.lower().split()                                                   # Exit logic
            exit_words = ["exit", "stop", "shutdown", "sat down", "shut down", "έξοδος", "σταμάτα", "σταμάτησε"]
            if any(word in user_text.lower() for word in exit_words) and len(words) < 15:
                speak("Goodbye!")
                break
                
            reply = get_gemini_response(user_text)
            speak(reply)
            
        except KeyboardInterrupt:
            break
        except Exception as e:
            error_count += 1
            print(f"Error: {e} ({error_count}/{MAX_ERRORS})")
            if error_count >= MAX_ERRORS:
                break