#!/usr/bin/env python3

import subprocess
import threading
import time

from fenrirscreenreader.core.i18n import _


class command:
    def __init__(self):
        pass

    def initialize(self, environment):
        self.env = environment
        self.testMessage = (
            "Voice test: The quick brown fox jumps over the lazy dog."
        )

    def shutdown(self):
        pass

    def get_description(self):
        return "Safe voice browser - cycles through voices without hanging"

    def run(self):
        try:
            self.env["runtime"]["OutputManager"].present_text(
                "Starting safe voice browser", interrupt=True
            )

            # Get modules with timeout protection
            modules = self.get_speechd_modules_with_timeout()
            if not modules:
                self.env["runtime"]["OutputManager"].present_text(
                    "No speech modules found", interrupt=True
                )
                return

            # Get current position from commandBuffer or start fresh
            module_index = self.env["commandBuffer"].get(
                "safeBrowserModuleIndex", 0
            )
            voice_index = self.env["commandBuffer"].get(
                "safeBrowserVoiceIndex", 0
            )

            # Ensure valid module index
            if module_index >= len(modules):
                module_index = 0

            current_module = modules[module_index]
            self.env["runtime"]["OutputManager"].present_text(
                f"Loading voices for {current_module}...", interrupt=True
            )

            # Get voices with timeout protection
            voices = self.get_module_voices_with_timeout(current_module)
            if not voices:
                self.env["runtime"]["OutputManager"].present_text(
                    f"No voices in {current_module}, trying next module",
                    interrupt=True,
                )
                module_index = (module_index + 1) % len(modules)
                self.env["commandBuffer"][
                    "safeBrowserModuleIndex"
                ] = module_index
                self.env["commandBuffer"]["safeBrowserVoiceIndex"] = 0
                return

            # Ensure valid voice index
            if voice_index >= len(voices):
                voice_index = 0

            current_voice = voices[voice_index]

            # Announce current selection
            self.env["runtime"]["OutputManager"].present_text(
                f"Module: {current_module} ({module_index + 1}/{len(modules)})",
                interrupt=True,
            )
            self.env["runtime"]["OutputManager"].present_text(
                f"Voice: {current_voice} ({voice_index + 1}/{len(voices)})",
                interrupt=True,
            )

            # Test voice in background thread to avoid blocking
            self.env["runtime"]["OutputManager"].present_text(
                "Testing voice...", interrupt=True
            )

            # Use threading to prevent freezing
            test_thread = threading.Thread(
                target=self.test_voice_async,
                args=(current_module, current_voice),
            )
            test_thread.daemon = True
            test_thread.start()

            # Store tested voice for apply command
            self.env["commandBuffer"]["lastTestedModule"] = current_module
            self.env["commandBuffer"]["lastTestedVoice"] = current_voice

            # Advance to next voice for next run
            voice_index += 1
            if voice_index >= len(voices):
                voice_index = 0
                module_index = (module_index + 1) % len(modules)

            # Store position for next run
            self.env["commandBuffer"]["safeBrowserModuleIndex"] = module_index
            self.env["commandBuffer"]["safeBrowserVoiceIndex"] = voice_index

            # Give instructions
            self.env["runtime"]["OutputManager"].present_text(
                "Run again for next voice, or use apply voice command",
                interrupt=True,
            )

        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                f"Voice browser error: {str(e)}", interrupt=True
            )
            self.env["runtime"]["OutputManager"].play_sound("Error")

    def test_voice_async(self, module, voice):
        """Test voice in background thread to avoid blocking"""
        try:
            # Run with strict timeout
            cmd = ["spd-say", "-o", module, "-y", voice, self.testMessage]
            result = subprocess.run(cmd, timeout=5, capture_output=True)

            # Schedule success sound for main thread
            if result.returncode == 0:
                # We can't call OutputManager from background thread safely
                # So we'll just let the main thread handle feedback
                pass

        except subprocess.TimeoutExpired:
            # Voice test timed out - this is okay, don't crash
            pass
        except Exception:
            # Any other error - also okay, don't crash
            pass

    def get_speechd_modules_with_timeout(self):
        """Get speech modules with timeout protection"""
        try:
            result = subprocess.run(
                ["spd-say", "-O"], capture_output=True, text=True, timeout=3
            )
            if result.returncode == 0:
                lines = result.stdout.strip().split("\n")
                modules = [line.strip() for line in lines[1:] if line.strip()]
                # Limit to first 10 modules to prevent overload
                return modules[:10]
        except subprocess.TimeoutExpired:
            self.env["runtime"]["OutputManager"].present_text(
                "Module detection timed out", interrupt=True
            )
        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                f"Module detection failed: {str(e)}", interrupt=True
            )
        return []

    def get_module_voices_with_timeout(self, module):
        """Get voices with timeout and limits"""
        try:
            result = subprocess.run(
                ["spd-say", "-o", module, "-L"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if result.returncode == 0:
                lines = result.stdout.strip().split("\n")
                voices = []
                for line in lines[1:]:
                    if not line.strip():
                        continue
                    if module.lower() == "espeak-ng":
                        voice = self.process_espeak_voice(line)
                        if voice:
                            voices.append(voice)
                    else:
                        voices.append(line.strip())

                # Limit voice count to prevent memory issues
                if len(voices) > 1000:
                    self.env["runtime"]["OutputManager"].present_text(
                        f"found {len(voices)} voices, limiting to first 1000",
                        interrupt=True,
                    )
                    voices = voices[:1000]

                return voices
        except subprocess.TimeoutExpired:
            self.env["runtime"]["OutputManager"].present_text(
                f"Voice detection for {module} timed out", interrupt=True
            )
        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                f"Voice detection failed: {str(e)}", interrupt=True
            )
        return []

    def process_espeak_voice(self, voiceLine):
        """Process espeak voice format"""
        try:
            parts = [p for p in voiceLine.split() if p]
            if len(parts) < 2:
                return None
            lang_code = parts[-2].lower()
            variant = parts[-1].lower()
            return (
                f"{lang_code}+{variant}"
                if variant and variant != "none"
                else lang_code
            )
        except Exception:
            return None

    def set_callback(self, callback):
        pass
