import asyncio
import argparse
import re
from typing import Any, Optional

try:
    from dotenv import load_dotenv
except ModuleNotFoundError:
    def load_dotenv() -> bool:  # type: ignore[override]
        return False

from .spotify_client import SpotifyClient
from .utils import parse_voice_command
from .voice_engine import VoiceEngine
from .ui.theme import ACCENT, APP_LOGO, APP_SUBTITLE, ERROR, SUCCESS, WARNING

RICH_AVAILABLE = True
try:
    from rich.console import Console
    from rich.live import Live

    from .ui.cli import generate_boot_banner, generate_layout
except ModuleNotFoundError:
    RICH_AVAILABLE = False

    def generate_boot_banner() -> str:
        return f"{APP_LOGO}\n{APP_SUBTITLE}\n"

    def generate_layout(current_song: str = "None", status: str = "Waiting...", last_command: str = "N/A") -> str:
        return (
            f"Now Playing: {current_song}\n"
            f"Status: {status}\n"
            f"Last Command: {last_command}\n"
        )


class PlainConsole:
    def clear(self) -> None:
        return

    def print(self, message: Any) -> None:
        print(_strip_markup(str(message)))


class NoopLive:
    def __enter__(self) -> "NoopLive":
        return self

    def __exit__(self, exc_type: Any, exc: Any, traceback: Any) -> bool:
        return False

    def update(self, _renderable: Any) -> None:
        return


def _strip_markup(message: str) -> str:
    return re.sub(r"\[/?[^\]]+\]", "", message)


console = Console() if RICH_AVAILABLE else PlainConsole()


def print_status(style: str, message: str) -> None:
    if RICH_AVAILABLE:
        console.print(f"[{style}]{message}[/]")
        return
    console.print(message)


def _update_view(live: Any, current_song: str, status: str, last_command: str) -> None:
    live.update(generate_layout(current_song=current_song, status=status, last_command=last_command))


async def handle_command(spotify: SpotifyClient, command: str) -> str:
    parsed = parse_voice_command(command)

    if parsed.action == "play":
        return await asyncio.to_thread(spotify.play_specific_track, str(parsed.value or ""))
    if parsed.action == "pause":
        return await asyncio.to_thread(spotify.toggle_playback, "pause")
    if parsed.action == "resume":
        return await asyncio.to_thread(spotify.toggle_playback, "resume")
    if parsed.action == "next":
        return await asyncio.to_thread(spotify.skip_track)
    if parsed.action == "previous":
        return await asyncio.to_thread(spotify.previous_track)
    if parsed.action == "volume_set":
        if parsed.value is not None:
            return await asyncio.to_thread(spotify.set_volume, int(parsed.value))
        return "Invalid volume value."
    if parsed.action == "volume_delta":
        if parsed.value is not None:
            return await asyncio.to_thread(spotify.adjust_volume, int(parsed.value))
        return "Invalid volume value."
    if parsed.action == "now_playing":
        song = await asyncio.to_thread(spotify.current_song)
        return f"Now playing: {song}"
    if parsed.action == "exit":
        return "Exiting hey-spotify."

    return (
        "I didn't understand the command. Try: "
        "play <song>, pause, resume, next, previous, volume <0-100>."
    )


def list_microphones() -> int:
    microphones = VoiceEngine.list_microphones()
    if not microphones:
        print_status(
            ERROR,
            "No microphones detected. Check OS microphone permissions and input device settings.",
        )
        return 1

    print_status(SUCCESS, "Detected microphone devices:")
    for index, microphone in enumerate(microphones):
        console.print(f"  [{index}] {microphone}")
    return 0


async def main(
    microphone_index: Optional[int] = None,
    voice_debug: bool = False,
    require_wake_word: bool = True,
    language: Optional[str] = None,
) -> None:
    load_dotenv()

    console.clear()
    console.print(generate_boot_banner())
    await asyncio.sleep(0.8)

    try:
        listener = VoiceEngine(
            microphone_index=microphone_index,
            debug=voice_debug,
            language=language,
        )
    except Exception as exc:
        print_status(ERROR, f"Microphone initialization failed: {exc}")
        return

    try:
        spotify = SpotifyClient()
    except Exception as exc:
        print_status(ERROR, f"Spotify client initialization failed: {exc}")
        return

    current_song = "Nothing playing"
    status = "Waiting for wake word"
    last_command = "N/A"

    print_status(SUCCESS, "hey-spotify is active. Say 'Hey Spotify' to begin.")
    print_status(ACCENT, f"Using microphone: {listener.selected_mic_name}")
    print_status(ACCENT, f"Voice language: {listener.language}")
    if not require_wake_word:
        print_status(WARNING, "Wake word disabled. Listening directly for commands.")

    live_context = (
        Live(
            generate_layout(current_song=current_song, status=status, last_command=last_command),
            console=console,
            refresh_per_second=6,
        )
        if RICH_AVAILABLE
        else NoopLive()
    )

    with live_context as live:
        while True:
            pending_command: Optional[str] = None
            if require_wake_word:
                status = "Listening for wake word"
                _update_view(live, current_song, status, last_command)

                wake_word_detected, pending_command = await asyncio.to_thread(
                    listener.listen_for_wake_event
                )
                if not wake_word_detected:
                    continue

                status = "Wake word detected"
                _update_view(live, current_song, status, last_command)
            else:
                status = "Listening for command"
                _update_view(live, current_song, status, last_command)

            command = pending_command or await asyncio.to_thread(listener.get_command)
            if not command:
                status = "Could not understand command"
                _update_view(live, current_song, status, last_command)
                print_status(WARNING, "Could not understand command.")
                continue

            last_command = command
            status = "Executing command"
            _update_view(live, current_song, status, last_command)

            try:
                result = await handle_command(spotify, command)
                if result == "Exiting hey-spotify.":
                    print_status(ACCENT, result)
                    break

                current_song = await asyncio.to_thread(spotify.current_song)
                status = result
                _update_view(live, current_song, status, last_command)
                print_status(ACCENT, result)
            except Exception as exc:
                status = "Command failed"
                _update_view(live, current_song, status, last_command)
                print_status(ERROR, f"Error: {exc}")


def cli() -> None:
    parser = argparse.ArgumentParser(description="EchoSpotify voice-controlled Spotify CLI")
    parser.add_argument(
        "--list-mics",
        action="store_true",
        help="List detected microphone devices and exit.",
    )
    parser.add_argument(
        "--mic-index",
        type=int,
        default=None,
        help="Microphone index to use (find indexes with --list-mics).",
    )
    parser.add_argument(
        "--voice-debug",
        action="store_true",
        help="Print recognized wake/command transcripts for debugging.",
    )
    parser.add_argument(
        "--no-wake-word",
        action="store_true",
        help="Skip wake-word detection and listen directly for commands.",
    )
    parser.add_argument(
        "--language",
        type=str,
        default=None,
        help="Speech recognition language (example: en-US). Overrides VOICE_LANGUAGE.",
    )
    args = parser.parse_args()

    if args.list_mics:
        raise SystemExit(list_microphones())

    try:
        asyncio.run(
            main(
                microphone_index=args.mic_index,
                voice_debug=args.voice_debug,
                require_wake_word=not args.no_wake_word,
                language=args.language,
            )
        )
    except KeyboardInterrupt:
        print_status(ERROR, "Shutting down... Goodbye.")


if __name__ == "__main__":
    cli()
