#!/usr/bin/env python3

import subprocess
import time

from fenrirscreenreader.core.i18n import _


class command:
    def __init__(self):
        pass

    def initialize(self, environment):
        self.env = environment
        self.testMessage = "This is a voice test. The quick brown fox jumps over the lazy dog."
        self.modules = []
        self.voices = []
        self.module_index = 0
        self.voice_index = 0
        self.browserActive = False
        self.originalBindings = {}
        self.lastAnnounceTime = 0

    def shutdown(self):
        pass

    def get_description(self):
        return "Interactive voice browser with arrow key navigation"

    def run(self):
        if self.browserActive:
            self.exit_voice_browser()
            return

        self.env["runtime"]["OutputManager"].present_text(
            "Starting voice browser", interrupt=True
        )

        # Load modules
        self.modules = self.get_speechd_modules()
        if not self.modules:
            self.env["runtime"]["OutputManager"].present_text(
                "No speech modules found", interrupt=True
            )
            self.env["runtime"]["OutputManager"].play_sound("Error")
            return

        # Set current module
        current_module = self.env["runtime"]["SettingsManager"].get_setting(
            "speech", "module"
        )
        if current_module and current_module in self.modules:
            self.module_index = self.modules.index(current_module)

        # Load voices
        self.load_voices_for_current_module()

        # Set current voice
        current_voice = self.env["runtime"]["SettingsManager"].get_setting(
            "speech", "voice"
        )
        if current_voice and current_voice in self.voices:
            self.voice_index = self.voices.index(current_voice)

        # Enter browser mode
        self.enter_voice_browser()
        self.announce_current_selection()

    def enter_voice_browser(self):
        """Enter voice browser mode with custom key bindings"""
        self.browserActive = True

        # Store original bindings
        self.originalBindings = self.env["bindings"].copy()

        # Override navigation keys for voice browsing
        # Use lambda to capture self and create bound methods
        self.env["bindings"][str([1, ["KEY_UP"]])] = "VOICE_BROWSER_PREV_VOICE"
        self.env["bindings"][
            str([1, ["KEY_DOWN"]])
        ] = "VOICE_BROWSER_NEXT_VOICE"
        self.env["bindings"][
            str([1, ["KEY_LEFT"]])
        ] = "VOICE_BROWSER_PREV_MODULE"
        self.env["bindings"][
            str([1, ["KEY_RIGHT"]])
        ] = "VOICE_BROWSER_NEXT_MODULE"
        self.env["bindings"][str([1, ["KEY_ENTER"]])] = "VOICE_BROWSER_TEST"
        self.env["bindings"][str([1, ["KEY_SPACE"]])] = "VOICE_BROWSER_APPLY"
        self.env["bindings"][str([1, ["KEY_ESC"]])] = "VOICE_BROWSER_EXIT"

        # Register browser commands
        self.register_browser_commands()

        self.env["runtime"]["OutputManager"].present_text(
            "Voice browser active", interrupt=True
        )
        self.env["runtime"]["OutputManager"].present_text(
            "Up/Down=voices, Left/Right=modules, Enter=test, Space=apply, Esc=exit",
            interrupt=True,
        )

    def register_browser_commands(self):
        """Register voice browser commands with the command manager"""
        # Create command objects for voice browser actions
        CommandManager = self.env["runtime"]["CommandManager"]

        # This is a hack - we'll store references to our methods in the environment
        # so the key handlers can call them
        if "voiceBrowserInstance" not in self.env["runtime"]:
            self.env["runtime"]["voiceBrowserInstance"] = self

    def exit_voice_browser(self):
        """Exit voice browser and restore normal key bindings"""
        if not self.browserActive:
            return

        self.browserActive = False

        # Restore original bindings
        self.env["bindings"] = self.originalBindings

        # Clean up
        if "voiceBrowserInstance" in self.env["runtime"]:
            del self.env["runtime"]["voiceBrowserInstance"]

        self.env["runtime"]["OutputManager"].present_text(
            "Voice browser exited", interrupt=True
        )

    def load_voices_for_current_module(self):
        """Load voices for current module"""
        if self.module_index < len(self.modules):
            module = self.modules[self.module_index]
            self.voices = self.get_module_voices(module)
            self.voice_index = 0  # Reset to first voice when changing modules

    def announce_current_selection(self):
        """Announce current module and voice"""
        # Throttle announcements to avoid spam
        now = time.time()
        if now - self.lastAnnounceTime < 0.3:
            return
        self.lastAnnounceTime = now

        if not self.modules or self.module_index >= len(self.modules):
            return

        module = self.modules[self.module_index]
        if self.voices and self.voice_index < len(self.voices):
            voice = self.voices[self.voice_index]
            self.env["runtime"]["OutputManager"].present_text(
                f"{module}: {voice} ({self.voice_index + 1}/{len(self.voices)})",
                interrupt=True,
            )
        else:
            self.env["runtime"]["OutputManager"].present_text(
                f"{module}: No voices", interrupt=True
            )

    def next_voice(self):
        """Move to next voice"""
        if not self.voices:
            return
        self.voice_index = (self.voice_index + 1) % len(self.voices)
        self.announce_current_selection()

    def prev_voice(self):
        """Move to previous voice"""
        if not self.voices:
            return
        self.voice_index = (self.voice_index - 1) % len(self.voices)
        self.announce_current_selection()

    def next_module(self):
        """Move to next module"""
        self.module_index = (self.module_index + 1) % len(self.modules)
        self.load_voices_for_current_module()
        self.announce_current_selection()

    def prev_module(self):
        """Move to previous module"""
        self.module_index = (self.module_index - 1) % len(self.modules)
        self.load_voices_for_current_module()
        self.announce_current_selection()

    def test_voice(self):
        """Test current voice"""
        if not self.voices or self.voice_index >= len(self.voices):
            self.env["runtime"]["OutputManager"].present_text(
                "No voice selected", interrupt=True
            )
            return

        module = self.modules[self.module_index]
        voice = self.voices[self.voice_index]

        self.env["runtime"]["OutputManager"].present_text(
            "Testing...", interrupt=True
        )
        if self.preview_voice(module, voice):
            # Store for apply command
            self.env["commandBuffer"]["lastTestedModule"] = module
            self.env["commandBuffer"]["lastTestedVoice"] = voice
            self.env["runtime"]["OutputManager"].play_sound("Accept")
        else:
            self.env["runtime"]["OutputManager"].play_sound("Error")

    def apply_voice(self):
        """Apply current voice to Fenrir"""
        if not self.voices or self.voice_index >= len(self.voices):
            return

        module = self.modules[self.module_index]
        voice = self.voices[self.voice_index]

        try:
            SettingsManager = self.env["runtime"]["SettingsManager"]
            SettingsManager.settings["speech"]["driver"] = "speechdDriver"
            SettingsManager.settings["speech"]["module"] = module
            SettingsManager.settings["speech"]["voice"] = voice

            if "SpeechDriver" in self.env["runtime"]:
                SpeechDriver = self.env["runtime"]["SpeechDriver"]
                SpeechDriver.shutdown()
                SpeechDriver.initialize(self.env)

                self.env["runtime"]["OutputManager"].present_text(
                    "Voice applied!", interrupt=True
                )
                self.env["runtime"]["OutputManager"].play_sound("Accept")

        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                "Apply failed", interrupt=True
            )
            self.env["runtime"]["OutputManager"].play_sound("Error")

    def preview_voice(self, module, voice):
        """Test voice with spd-say"""
        try:
            cmd = ["spd-say", "-o", module, "-y", voice, self.testMessage]
            result = subprocess.run(cmd, timeout=10)
            return result.returncode == 0
        except Exception:
            return False

    def get_speechd_modules(self):
        """Get available speech modules"""
        try:
            result = subprocess.run(
                ["spd-say", "-O"], capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                lines = result.stdout.strip().split("\n")
                return [line.strip() for line in lines[1:] if line.strip()]
        except Exception:
            pass
        return []

    def get_module_voices(self, module):
        """Get voices for module"""
        try:
            result = subprocess.run(
                ["spd-say", "-o", module, "-L"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            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())
                return voices
        except Exception:
            pass
        return []

    def process_espeak_voice(self, voiceLine):
        """Process espeak voice format"""
        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
        )

    def set_callback(self, callback):
        pass
