#!/usr/bin/env python3

import importlib.util
import os
import subprocess
import time

from fenrirscreenreader.core import debug


class DynamicVoiceCommand:
    """Dynamic command class for voice selection"""

    def __init__(self, module, voice, env):
        self.module = module
        self.voice = voice
        self.env = env
        self.testMessage = "This is a voice test. The quick brown fox jumps over the lazy dog."

    def initialize(self, environment):
        self.env = environment

    def shutdown(self):
        pass

    def get_description(self):
        return f"Select voice: {self.voice}"

    def run(self):
        try:
            self.env["runtime"]["OutputManager"].present_text(
                f"Testing voice {self.voice} from {self.module}. Please wait.",
                interrupt=True,
            )

            # Brief pause before testing to avoid speech overlap
            time.sleep(0.5)

            # Test voice
            testResult, errorMsg = self.test_voice()
            if testResult:
                self.env["runtime"]["OutputManager"].present_text(
                    "Voice test completed successfully. Navigate to Apply Tested Voice to use this voice.",
                    interrupt=False,
                    flush=False,
                )

                # Store for confirmation (use same variables as
                # apply_tested_voice.py)
                self.env["commandBuffer"]["lastTestedModule"] = self.module
                self.env["commandBuffer"]["lastTestedVoice"] = self.voice
                self.env["commandBuffer"]["pendingVoiceModule"] = self.module
                self.env["commandBuffer"]["pendingVoiceVoice"] = self.voice
                self.env["commandBuffer"]["voiceTestCompleted"] = True
            else:
                self.env["runtime"]["OutputManager"].present_text(
                    f"Voice test failed: {errorMsg}",
                    interrupt=False,
                    flush=False,
                )

        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                f"Voice selection error: {str(e)}",
                interrupt=False,
                flush=False,
            )

    def test_voice(self):
        """Test voice with spd-say"""
        try:
            cmd = [
                "spd-say",
                "-C",
                "-w",
                "-o",
                self.module,
                "-y",
                self.voice,
                self.testMessage,
            ]
            result = subprocess.run(
                cmd, timeout=8, capture_output=True, text=True
            )
            if result.returncode == 0:
                return True, "Voice test successful"
            else:
                error_msg = (
                    result.stderr.strip()
                    if result.stderr
                    else f"Command failed with return code {result.returncode}"
                )
                return False, error_msg
        except subprocess.TimeoutExpired:
            return False, "Voice test timed out"
        except Exception as e:
            return False, f"Error running voice test: {str(e)}"

    def set_callback(self, callback):
        pass


class DynamicApplyVoiceCommand:
    """Command to apply the tested voice"""

    def __init__(self, env):
        self.env = env

    def initialize(self, environment):
        self.env = environment

    def shutdown(self):
        pass

    def get_description(self):
        return "Apply tested voice to Fenrir"

    def run(self):
        try:
            if not self.env["commandBuffer"].get("voiceTestCompleted", False):
                self.env["runtime"]["OutputManager"].present_text(
                    "No voice has been tested yet", interrupt=True
                )
                return

            module = self.env["commandBuffer"]["pendingVoiceModule"]
            voice = self.env["commandBuffer"]["pendingVoiceVoice"]

            self.env["runtime"]["OutputManager"].present_text(
                f"Applying {voice} from {module}", interrupt=True
            )

            # Debug: Show current settings
            settings_manager = self.env["runtime"]["SettingsManager"]
            current_module = settings_manager.get_setting("speech", "module")
            current_voice = settings_manager.get_setting("speech", "voice")
            self.env["runtime"]["OutputManager"].present_text(
                f"Current: {current_voice} from {current_module}",
                interrupt=False,
                flush=False,
            )

            # Apply to runtime settings with fallback
            settings_manager = self.env["runtime"]["SettingsManager"]

            # Store old values for safety
            old_driver = settings_manager.get_setting("speech", "driver")
            old_module = settings_manager.get_setting("speech", "module")
            old_voice = settings_manager.get_setting("speech", "voice")

            try:
                # Apply new settings to runtime only (use set_setting to update
                # settingArgDict)
                settings_manager.set_setting(
                    "speech", "driver", "speechdDriver"
                )
                settings_manager.set_setting("speech", "module", module)
                settings_manager.set_setting("speech", "voice", voice)

                # Apply settings to speech driver directly
                if "SpeechDriver" in self.env["runtime"]:
                    SpeechDriver = self.env["runtime"]["SpeechDriver"]

                    # Get current module to see if we're changing modules
                    current_module = settings_manager.get_setting(
                        "speech", "module"
                    )
                    module_changing = current_module != module

                    # Set module and voice on driver instance first
                    SpeechDriver.set_module(module)
                    SpeechDriver.set_voice(voice)

                    if module_changing:
                        # Module change requires reinitializing the speech
                        # driver
                        self.env["runtime"]["OutputManager"].present_text(
                            f"Switching from {current_module} to {module} module",
                            interrupt=True,
                        )
                        SpeechDriver.shutdown()
                        SpeechDriver.initialize(self.env)
                        # Re-set after initialization
                        SpeechDriver.set_module(module)
                        SpeechDriver.set_voice(voice)
                        self.env["runtime"]["OutputManager"].present_text(
                            "Speech driver reinitialized", interrupt=True
                        )

                    # Debug: verify what was actually set
                    self.env["runtime"]["OutputManager"].present_text(
                        f"Speech driver now has module: {SpeechDriver.module}, voice: {SpeechDriver.voice}",
                        interrupt=True,
                    )

                    # Force application by speaking a test message
                    self.env["runtime"]["OutputManager"].present_text(
                        "Voice applied successfully! You should hear this in the new voice.",
                        interrupt=True,
                    )

                    # Brief pause then more speech to test
                    time.sleep(1)
                    self.env["runtime"]["OutputManager"].present_text(
                        "Use save settings to make permanent", interrupt=True
                    )

                # Clear pending state
                self.env["commandBuffer"]["voiceTestCompleted"] = False

                # Exit vmenu after successful application
                self.env["runtime"]["VmenuManager"].set_active(False)

            except Exception as e:
                # Revert on failure
                settings_manager.settings["speech"]["driver"] = old_driver
                settings_manager.settings["speech"]["module"] = old_module
                settings_manager.settings["speech"]["voice"] = old_voice

                # Try to reinitialize with old settings
                if "SpeechDriver" in self.env["runtime"]:
                    try:
                        SpeechDriver = self.env["runtime"]["SpeechDriver"]
                        SpeechDriver.shutdown()
                        SpeechDriver.initialize(self.env)
                    except Exception as e:
                        self.env["runtime"]["DebugManager"].write_debug_out(
                            "dynamicVoiceMenu: Error reinitializing speech driver: "
                            + str(e),
                            debug.DebugLevel.ERROR,
                        )

                self.env["runtime"]["OutputManager"].present_text(
                    f"Failed to apply voice, reverted: {str(e)}",
                    interrupt=False,
                    flush=False,
                )

        except Exception as e:
            self.env["runtime"]["OutputManager"].present_text(
                f"Apply voice error: {str(e)}", interrupt=False, flush=False
            )

    def set_callback(self, callback):
        pass


def add_dynamic_voice_menus(VmenuManager):
    """Add dynamic voice menus to vmenu system"""
    try:
        env = VmenuManager.env

        # Get speech modules
        modules = get_speechd_modules()
        if not modules:
            return

        # Create voice browser submenu
        voice_browser_menu = {}

        # Add apply voice command
        apply_command = DynamicApplyVoiceCommand(env)
        voice_browser_menu["Apply Tested Voice Action"] = apply_command

        # Add modules as submenus
        for module in modules[
            :8
        ]:  # Limit to 8 modules to keep menu manageable
            module_menu = {}

            # Get voices for this module
            voices = get_module_voices(module)
            if voices:

                # Add voice commands
                for voice in voices:
                    voice_command = DynamicVoiceCommand(module, voice, env)
                    module_menu[f"{voice} Action"] = voice_command
            else:
                module_menu["No voices available Action"] = (
                    create_info_command(f"No voices found for {module}", env)
                )

            voice_browser_menu[f"{module} Menu"] = module_menu

        # Add to main menu dict
        VmenuManager.menuDict["Voice Browser Menu"] = voice_browser_menu

    except Exception as e:
        # Use debug manager instead of print for error logging
        if "DebugManager" in env["runtime"]:
            env["runtime"]["DebugManager"].write_debug_out(
                f"Error creating dynamic voice menus: {e}",
                debug.DebugLevel.ERROR,
            )
        else:
            print(f"Error creating dynamic voice menus: {e}")


def create_info_command(message, env):
    """Create a simple info command"""

    class InfoCommand:
        def __init__(self, message, env):
            self.message = message
            self.env = env

        def initialize(self, environment):
            pass

        def shutdown(self):
            pass

        def get_description(self):
            return self.message

        def run(self):
            self.env["runtime"]["OutputManager"].present_text(
                self.message, interrupt=True
            )

        def set_callback(self, callback):
            pass

    return InfoCommand(message, env)


def get_speechd_modules():
    """Get available speech modules"""
    try:
        result = subprocess.run(
            ["spd-say", "-O"], capture_output=True, text=True, timeout=5
        )
        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(module):
    """Get voices for a module"""
    try:
        result = subprocess.run(
            ["spd-say", "-o", module, "-L"],
            capture_output=True,
            text=True,
            timeout=8,
        )
        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 = process_espeak_voice(line)
                    if voice:
                        voices.append(voice)
                else:
                    voices.append(line.strip())
            return voices
    except Exception:
        pass
    return []


def process_espeak_voice(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
