import os
import sys
import subprocess
import tkinter as tk
from tkinter import scrolledtext, messagebox, filedialog, ttk
import threading
import sounddevice as sd
import numpy as np
from scipy.io.wavfile import write
from faster_whisper import WhisperModel
import time
import locale

# --- LIBRARY PATH CONFIGURATION ---
# Adds the private 'lib' directory to sys.path to find dependencies
base_dir = os.path.dirname(os.path.abspath(__file__))
lib_dir = os.path.join(base_dir, "lib")

if os.path.exists(lib_dir):
    sys.path.insert(0, lib_dir)

# Load translations from external file
try:
    from translations import LANG_DATA
except ImportError:
    LANG_DATA = {'en': {'title': "FUSS SpeechToText", 'status_error': "Error"}}

# --- GET SYSTEM LANGUAGE SAFELY ---
try:
    locale.setlocale(locale.LC_ALL, "")
    sys_lang_full = locale.getlocale()[0]
    sys_lang = sys_lang_full[:2].lower() if sys_lang_full else 'en'
except Exception:
    sys_lang = 'en'

T = LANG_DATA.get(sys_lang, LANG_DATA['en'])

# Global constants
MODEL_PATH = "/opt/fuss-speechtotext/models"
TEMP_AUDIO = "/tmp/fuss_stt.wav"

class FussListenGUI:
    def __init__(self, root):
        self.root = root
        self.root.title(T['title'])
        
        # --- 1. WINDOW CENTERING & GEOMETRY ---
        window_width = 700
        window_height = 700
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        center_x = int(screen_width/2 - window_width/2)
        center_y = int(screen_height/2 - window_height/2)
        
        self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
        self.root.resizable(True, True) 
        self.root.configure(bg="#e1f3fe")
        
        self.header_bg = "#bdcdd7"
        self.font_bold = ("Arial", 11, "bold")
        
        # State variables
        self.is_recording = False
        self.audio_data = []
        self.model = None
        self.selected_lang = tk.StringVar(value=sys_lang if sys_lang in ['it','de','en','fr'] else "it")

        # --- HEADER ---
        self.header = tk.Frame(root, bg=self.header_bg, pady=15)
        self.header.pack(fill=tk.X)
        tk.Label(self.header, text=T['title'].upper(), bg=self.header_bg, 
                 fg="#4a5a6a", font=("Arial", 16, "bold")).pack()
        tk.Label(self.header, text=T['subtitle'], 
                 bg=self.header_bg, fg="#4a5a6a", font=("Arial", 9, "bold")).pack()

        # --- LANGUAGE SELECTOR ---
        self.lang_container = tk.Frame(root, bg="#e1f3fe", pady=10)
        self.lang_container.pack(fill=tk.X)
        self.lang_inner = tk.Frame(self.lang_container, bg="#e1f3fe")
        self.lang_inner.pack()
        tk.Label(self.lang_inner, text=T['label_lang'], bg="#e1f3fe", font=self.font_bold, fg="#4a5a6a").pack(side=tk.LEFT, padx=(0, 15))
        for text, val in [("Italiano", "it"), ("Deutsch", "de"), ("English", "en"), ("Français", "fr")]:
            tk.Radiobutton(self.lang_inner, text=text, variable=self.selected_lang, value=val, 
                           bg="#e1f3fe", activebackground="#e1f3fe", font=("Arial", 10), padx=5).pack(side=tk.LEFT)

        # --- STATUS & PROGRESS BAR ---
        self.status_label = tk.Label(root, text=T['status_init'], bg="#e1f3fe", fg="#d35400", font=self.font_bold)
        self.status_label.pack(pady=5)
        self.progress = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=600, mode='determinate')
        self.progress.pack(pady=5)

        # --- TEXT AREA ---
        self.text_frame = tk.Frame(root, bg="#e1f3fe", padx=40)
        self.text_frame.pack(fill=tk.BOTH, expand=True)
        self.text_area = scrolledtext.ScrolledText(self.text_frame, wrap=tk.WORD, font=("Arial", 12), height=10, bd=1, relief="solid")
        self.text_area.pack(fill=tk.BOTH, expand=True)
        
        # Context Menu
        self.context_menu = tk.Menu(self.text_area, tearoff=0)
        self.context_menu.add_command(label=T['menu_cut'], command=lambda: self.text_area.event_generate("<<Cut>>"))
        self.context_menu.add_command(label=T['menu_copy'], command=lambda: self.text_area.event_generate("<<Copy>>"))
        self.context_menu.add_command(label=T['menu_paste'], command=lambda: self.text_area.event_generate("<<Paste>>"))
        self.context_menu.add_separator()
        self.context_menu.add_command(label=T['menu_select_all'], command=self.select_all)
        self.text_area.bind("<Button-3>", lambda e: self.context_menu.tk_popup(e.x_root, e.y_root))

        # --- FOOTER BUTTONS ---
        self.footer = tk.Frame(root, bg="#e1f3fe", padx=40, pady=20)
        self.footer.pack(fill=tk.X, side=tk.BOTTOM)
        self.footer.columnconfigure(0, weight=1, uniform="group1")
        self.footer.columnconfigure(1, weight=1, uniform="group1")

        self.btn_start = tk.Button(self.footer, text=T['btn_record'], bg="#569d6b", fg="white", font=self.font_bold, relief="flat", command=self.start_recording, state=tk.DISABLED)
        self.btn_start.grid(row=0, column=0, sticky="nsew", padx=30, pady=5, ipady=5)
        
        self.btn_stop = tk.Button(self.footer, text=T['btn_stop'], bg="#bc6c63", fg="white", font=self.font_bold, relief="flat", command=self.stop_recording, state=tk.DISABLED)
        self.btn_stop.grid(row=0, column=1, sticky="nsew", padx=30, pady=5, ipady=5)

        self.btn_load = tk.Button(self.footer, text=T['btn_load'], bg="#7a8a99", fg="white", font=self.font_bold, relief="flat", command=self.load_file_dialog, state=tk.DISABLED)
        self.btn_load.grid(row=1, column=0, sticky="nsew", padx=30, pady=5, ipady=5)
        
        self.btn_clear = tk.Button(self.footer, text=T['btn_clear'], bg="#bdcdd7", fg="#4a5a6a", font=self.font_bold, relief="flat", command=self.clear_text)
        self.btn_clear.grid(row=1, column=1, sticky="nsew", padx=30, pady=5, ipady=5)

        self.btn_exit = tk.Button(self.footer, text=T['btn_exit'], bg="#4a5a6a", fg="white", font=self.font_bold, relief="flat", command=self.root.destroy, width=15)
        self.btn_exit.grid(row=2, column=0, columnspan=2, pady=(15, 5), ipady=5)

        # Start loading model
        threading.Thread(target=self.load_model, daemon=True).start()

    def select_all(self):
        self.text_area.tag_add("sel", "1.0", "end")
        self.text_area.mark_set("insert", "1.0")
        self.text_area.see("insert")
        return "break"

    def clear_text(self):
        if messagebox.askyesno(T['msg_clear_title'], T['msg_clear_body']):
            self.text_area.delete(1.0, tk.END)

    def load_model(self):
        try:
            self.model = WhisperModel(MODEL_PATH, device="cpu", compute_type="int8", cpu_threads=0, num_workers=2)
            self.root.after(0, self.model_ready)
        except Exception as e:
            self.status_label.config(text=f"{T['status_error']}: {e}", fg="red")

    def model_ready(self):
        self.status_label.config(text=T['status_ready'], fg="#27ae60")
        self.btn_start.config(state=tk.NORMAL)
        self.btn_load.config(state=tk.NORMAL)

    def start_recording(self):
        self.audio_data = []
        self.is_recording = True
        self.btn_start.config(state=tk.DISABLED, bg="#95a5a6")
        self.btn_stop.config(state=tk.NORMAL)
        self.status_label.config(text=T['status_listening'], fg="#c0392b")
        self.progress['value'] = 0
        threading.Thread(target=self.record_loop, daemon=True).start()

    def record_loop(self):
        try:
            with sd.InputStream(device=None, samplerate=16000, channels=1, callback=lambda i,f,t,s: self.audio_data.append(i.copy())):
                while self.is_recording: sd.sleep(100)
        except Exception as e:
            self.is_recording = False
            self.root.after(0, lambda: messagebox.showerror("Audio Error", str(e)))

    def stop_recording(self):
        self.is_recording = False
        self.btn_stop.config(state=tk.DISABLED)
        self.status_label.config(text=T['status_processing'], fg="#2980b9")
        self.progress.start(10)
        threading.Thread(target=self.process_audio, daemon=True).start()

    def process_audio(self):
        if not self.audio_data: return
        audio = np.concatenate(self.audio_data, axis=0)
        
        # Simple volume check
        if np.max(np.abs(audio)) < 0.01:
            self.root.after(0, self.audio_silent)
            return

        write(TEMP_AUDIO, 16000, audio)
        self.run_transcription(TEMP_AUDIO)
        if os.path.exists(TEMP_AUDIO): os.remove(TEMP_AUDIO)

    def audio_silent(self):
        self.status_label.config(text=T['status_no_audio'], fg="red")
        self.btn_start.config(state=tk.NORMAL, bg="#569d6b")
        self.progress.stop()

    def load_file_dialog(self):
        path = None
        try:
            cmd = ["zenity", "--file-selection", "--title=" + T['zenity_title'],
                   "--file-filter=" + T['zenity_filter'] + " (mp3 wav m4a flac ogg) | *.mp3 *.wav *.m4a *.flac *.ogg",
                   "--file-filter=All files | *"]
            result = subprocess.run(cmd, capture_output=True, text=True)
            if result.returncode == 0: path = result.stdout.strip()
        except FileNotFoundError:
            path = filedialog.askopenfilename(filetypes=[("Audio", "*.mp3 *.wav *.m4a *.flac *.ogg"), ("All files", "*.*")])
        if path and os.path.exists(path):
            self.status_label.config(text=T['status_processing'], fg="#2980b9")
            self.progress.start(10)
            threading.Thread(target=self.run_transcription, args=(path,), daemon=True).start()

    def run_transcription(self, path):
        try:
            lang = self.selected_lang.get()
            current_t = LANG_DATA.get(lang, T)
            selected_prompt = current_t.get('ai_prompt', "Transcription.")

            # Optimized settings to prevent loops and hallucinations
            # These settings balance speed and modern language accuracy.
            p = {
                "beam_size": 5,                     # Optimal value: high accuracy with low latency
                "patience": 1.0,                    # Standard search patience; prevents over-calculating
                "temperature": 0,                   # Deterministic decoding to ensure consistent phonetic results
                "condition_on_previous_text": True, # Uses previous context to improve punctuation and sentence flow
                "vad_filter": True,                 # Filters out background noise and non-speech audio
                "vad_parameters": dict(
                    min_silence_duration_ms=1000    # Threshold to ignore silence gaps longer than 1 second
                ),
                "repetition_penalty": 1.1,          # Discourages the model from repeating the same words
                "no_repeat_ngram_size": 4,          # Prevents infinite loops by blocking repeated 4-word sequences
                "initial_prompt": selected_prompt   # Injects a stylistic and orthographic guide for the AI
            }
            
            segments, _ = self.model.transcribe(path, language=lang, **p)
            full_text = " ".join([s.text for s in segments]).strip()
            
            # Final cleanup for common Whisper artifacts
            hallucinations = ["Grazie a tutti", "Sottotitoli", "Thanks for watching", "Vielen Dank", "Grazie per la visione"]
            for h in hallucinations:
                if h.lower() in full_text.lower() and len(full_text) < 60:
                    full_text = ""
                    break

            self.root.after(0, self.update_ui_post, full_text)
        except Exception as e:
            self.root.after(0, self.error_transc, e)

    def update_ui_post(self, text):
        if text:
            prefix = "\n\n" if self.text_area.get(1.0, tk.END).strip() else ""
            self.text_area.insert(tk.END, prefix + text)
            self.text_area.see(tk.END)
            self.root.clipboard_clear()
            self.root.clipboard_append(text)
            self.status_label.config(text=T['status_copied'], fg="#27ae60")
        else:
            self.status_label.config(text=T['status_no_text'], fg="#d35400")
        self.progress.stop()
        self.progress['value'] = 100
        self.btn_start.config(state=tk.NORMAL, bg="#569d6b")
        self.btn_load.config(state=tk.NORMAL)

    def error_transc(self, e):
        self.status_label.config(text=T['status_error'], fg="red")
        self.progress.stop()
        messagebox.showerror("AI Error", str(e))

if __name__ == "__main__":
    root = tk.Tk()
    app = FussListenGUI(root)
    root.mainloop()
