#!/usr/bin/env python3
"""Discord Voice Doctor — diagnostic tool for voice channel support.

Checks all dependencies, configuration, and bot permissions needed
for Discord voice mode to work correctly.

Usage:
    python scripts/discord-voice-doctor.py
    .venv/bin/python scripts/discord-voice-doctor.py
"""

import os
import sys
import shutil
from pathlib import Path

# Resolve project root
SCRIPT_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = SCRIPT_DIR.parent
sys.path.insert(0, str(PROJECT_ROOT))

HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
ENV_FILE = HERMES_HOME / ".env"

OK = "\033[92m\u2713\033[0m"
FAIL = "\033[91m\u2717\033[0m"
WARN = "\033[93m!\033[0m"

# Track whether discord.py is available for later sections
_discord_available = False


def mask(value):
    """Mask sensitive value: show only first 4 chars."""
    if not value or len(value) < 8:
        return "****"
    return f"{value[:4]}{'*' * (len(value) - 4)}"


def check(label, ok, detail=""):
    symbol = OK if ok else FAIL
    msg = f"  {symbol} {label}"
    if detail:
        msg += f"  ({detail})"
    print(msg)
    return ok


def warn(label, detail=""):
    msg = f"  {WARN} {label}"
    if detail:
        msg += f"  ({detail})"
    print(msg)


def section(title):
    print(f"\n\033[1m{title}\033[0m")


def check_packages():
    """Check Python package dependencies. Returns True if all critical deps OK."""
    global _discord_available
    section("Python Packages")
    ok = True

    # discord.py
    try:
        import discord
        _discord_available = True
        check("discord.py", True, f"v{discord.__version__}")
    except ImportError:
        check("discord.py", False, "pip install discord.py[voice]")
        ok = False

    # PyNaCl
    try:
        import nacl
        ver = getattr(nacl, "__version__", "unknown")
        try:
            import nacl.secret
            nacl.secret.Aead(bytes(32))
            check("PyNaCl", True, f"v{ver}")
        except (AttributeError, Exception):
            check("PyNaCl (Aead)", False, f"v{ver} — need >=1.5.0")
            ok = False
    except ImportError:
        check("PyNaCl", False, "pip install PyNaCl>=1.5.0")
        ok = False

    # davey (DAVE E2EE)
    try:
        import davey
        check("davey (DAVE E2EE)", True, f"v{getattr(davey, '__version__', '?')}")
    except ImportError:
        check("davey (DAVE E2EE)", False, "pip install davey")
        ok = False

    # Optional: local STT
    try:
        import faster_whisper
        check("faster-whisper (local STT)", True)
    except ImportError:
        warn("faster-whisper (local STT)", "not installed — local STT unavailable")

    # Optional: TTS providers
    try:
        import edge_tts
        check("edge-tts", True)
    except ImportError:
        warn("edge-tts", "not installed — edge TTS unavailable")

    try:
        import elevenlabs
        check("elevenlabs SDK", True)
    except ImportError:
        warn("elevenlabs SDK", "not installed — premium TTS unavailable")

    return ok


def check_system_tools():
    """Check system-level tools (opus, ffmpeg). Returns True if all OK."""
    section("System Tools")
    ok = True

    # Opus codec
    if _discord_available:
        try:
            import discord
            opus_loaded = discord.opus.is_loaded()
            if not opus_loaded:
                import ctypes.util
                opus_path = ctypes.util.find_library("opus")
                if not opus_path:
                    # Platform-specific fallback paths
                    candidates = [
                        "/opt/homebrew/lib/libopus.dylib",   # macOS Apple Silicon
                        "/usr/local/lib/libopus.dylib",      # macOS Intel
                        "/usr/lib/x86_64-linux-gnu/libopus.so.0",  # Debian/Ubuntu x86
                        "/usr/lib/aarch64-linux-gnu/libopus.so.0", # Debian/Ubuntu ARM
                        "/usr/lib/libopus.so",               # Arch Linux
                        "/usr/lib64/libopus.so",             # RHEL/Fedora
                    ]
                    for p in candidates:
                        if os.path.isfile(p):
                            opus_path = p
                            break
                if opus_path:
                    discord.opus.load_opus(opus_path)
                    opus_loaded = discord.opus.is_loaded()
            if opus_loaded:
                check("Opus codec", True)
            else:
                check("Opus codec", False, "brew install opus / apt install libopus0")
                ok = False
        except Exception as e:
            check("Opus codec", False, str(e))
            ok = False
    else:
        warn("Opus codec", "skipped — discord.py not installed")

    # ffmpeg
    ffmpeg_path = shutil.which("ffmpeg")
    if ffmpeg_path:
        check("ffmpeg", True, ffmpeg_path)
    else:
        check("ffmpeg", False, "brew install ffmpeg / apt install ffmpeg")
        ok = False

    return ok


def check_env_vars():
    """Check environment variables. Returns (ok, token, groq_key, eleven_key)."""
    section("Environment Variables")

    # Load .env
    try:
        from dotenv import load_dotenv
        if ENV_FILE.exists():
            load_dotenv(ENV_FILE)
    except ImportError:
        pass

    ok = True

    token = os.getenv("DISCORD_BOT_TOKEN", "")
    if token:
        check("DISCORD_BOT_TOKEN", True, mask(token))
    else:
        check("DISCORD_BOT_TOKEN", False, "not set")
        ok = False

    # Allowed users — resolve usernames if possible
    allowed = os.getenv("DISCORD_ALLOWED_USERS", "")
    if allowed:
        users = [u.strip() for u in allowed.split(",") if u.strip()]
        user_labels = []
        for uid in users:
            label = mask(uid)
            if token and uid.isdigit():
                try:
                    import requests
                    r = requests.get(
                        f"https://discord.com/api/v10/users/{uid}",
                        headers={"Authorization": f"Bot {token}"},
                        timeout=3,
                    )
                    if r.status_code == 200:
                        label = f"{r.json().get('username', '?')} ({mask(uid)})"
                except Exception:
                    pass
            user_labels.append(label)
        check("DISCORD_ALLOWED_USERS", True, f"{len(users)} user(s): {', '.join(user_labels)}")
    else:
        warn("DISCORD_ALLOWED_USERS", "not set — all users can use voice")

    groq_key = os.getenv("GROQ_API_KEY", "")
    eleven_key = os.getenv("ELEVENLABS_API_KEY", "")

    if groq_key:
        check("GROQ_API_KEY (STT)", True, mask(groq_key))
    else:
        warn("GROQ_API_KEY", "not set — Groq STT unavailable")

    if eleven_key:
        check("ELEVENLABS_API_KEY (TTS)", True, mask(eleven_key))
    else:
        warn("ELEVENLABS_API_KEY", "not set — ElevenLabs TTS unavailable")

    return ok, token, groq_key, eleven_key


def check_config(groq_key, eleven_key):
    """Check hermes config.yaml."""
    section("Configuration")

    config_path = HERMES_HOME / "config.yaml"
    if config_path.exists():
        try:
            import yaml
            with open(config_path) as f:
                cfg = yaml.safe_load(f) or {}

            stt_provider = cfg.get("stt", {}).get("provider", "local")
            tts_provider = cfg.get("tts", {}).get("provider", "edge")
            check("STT provider", True, stt_provider)
            check("TTS provider", True, tts_provider)

            if stt_provider == "groq" and not groq_key:
                warn("STT config says groq but GROQ_API_KEY is missing")
            if stt_provider == "mistral" and not os.getenv("MISTRAL_API_KEY"):
                warn("STT config says mistral but MISTRAL_API_KEY is missing")
            if tts_provider == "elevenlabs" and not eleven_key:
                warn("TTS config says elevenlabs but ELEVENLABS_API_KEY is missing")
            if tts_provider == "mistral" and not os.getenv("MISTRAL_API_KEY"):
                warn("TTS config says mistral but MISTRAL_API_KEY is missing")
        except Exception as e:
            warn("config.yaml", f"parse error: {e}")
    else:
        warn("config.yaml", "not found — using defaults")

    # Voice mode state
    voice_mode_path = HERMES_HOME / "gateway_voice_mode.json"
    if voice_mode_path.exists():
        try:
            import json
            modes = json.loads(voice_mode_path.read_text())
            off_count = sum(1 for v in modes.values() if v == "off")
            all_count = sum(1 for v in modes.values() if v == "all")
            check("Voice mode state", True, f"{all_count} on, {off_count} off, {len(modes)} total")
        except Exception:
            warn("Voice mode state", "parse error")
    else:
        check("Voice mode state", True, "no saved state (fresh)")


def check_bot_permissions(token):
    """Check bot permissions via Discord API. Returns True if all OK."""
    section("Bot Permissions")

    if not token:
        warn("Bot permissions", "no token — skipping")
        return True

    try:
        import requests
    except ImportError:
        warn("Bot permissions", "requests not installed — skipping")
        return True

    VOICE_PERMS = {
        "Priority Speaker":      8,
        "Stream":                9,
        "View Channel":         10,
        "Send Messages":        11,
        "Embed Links":          14,
        "Attach Files":         15,
        "Read Message History": 16,
        "Connect":              20,
        "Speak":                21,
        "Mute Members":         22,
        "Deafen Members":       23,
        "Move Members":         24,
        "Use VAD":              25,
        "Send Voice Messages":  46,
    }
    REQUIRED_PERMS = {"Connect", "Speak", "View Channel", "Send Messages"}
    ok = True

    try:
        headers = {"Authorization": f"Bot {token}"}
        r = requests.get("https://discord.com/api/v10/users/@me", headers=headers, timeout=5)

        if r.status_code == 401:
            check("Bot login", False, "invalid token (401)")
            return False
        if r.status_code != 200:
            check("Bot login", False, f"HTTP {r.status_code}")
            return False

        bot = r.json()
        bot_name = bot.get("username", "?")
        check("Bot login", True, f"{bot_name[:3]}{'*' * (len(bot_name) - 3)}")

        # Check guilds
        r2 = requests.get("https://discord.com/api/v10/users/@me/guilds", headers=headers, timeout=5)
        if r2.status_code != 200:
            warn("Guilds", f"HTTP {r2.status_code}")
            return ok

        guilds = r2.json()
        check("Guilds", True, f"{len(guilds)} guild(s)")

        for g in guilds[:5]:
            perms = int(g.get("permissions", 0))
            is_admin = bool(perms & (1 << 3))

            if is_admin:
                print(f"    {OK} {g['name']}: Administrator (all permissions)")
                continue

            has = []
            missing = []
            for name, bit in sorted(VOICE_PERMS.items(), key=lambda x: x[1]):
                if perms & (1 << bit):
                    has.append(name)
                elif name in REQUIRED_PERMS:
                    missing.append(name)

            if missing:
                print(f"    {FAIL} {g['name']}: missing {', '.join(missing)}")
                ok = False
            else:
                print(f"    {OK} {g['name']}: {', '.join(has)}")

    except requests.exceptions.Timeout:
        warn("Bot permissions", "Discord API timeout")
    except requests.exceptions.ConnectionError:
        warn("Bot permissions", "cannot reach Discord API")
    except Exception as e:
        warn("Bot permissions", f"check failed: {e}")

    return ok


def main():
    print()
    print("\033[1m" + "=" * 50 + "\033[0m")
    print("\033[1m  Discord Voice Doctor\033[0m")
    print("\033[1m" + "=" * 50 + "\033[0m")

    all_ok = True

    all_ok &= check_packages()
    all_ok &= check_system_tools()
    env_ok, token, groq_key, eleven_key = check_env_vars()
    all_ok &= env_ok
    check_config(groq_key, eleven_key)
    all_ok &= check_bot_permissions(token)

    # Summary
    print()
    print("\033[1m" + "-" * 50 + "\033[0m")
    if all_ok:
        print(f"  {OK} \033[92mAll checks passed — voice mode ready!\033[0m")
    else:
        print(f"  {FAIL} \033[91mSome checks failed — fix issues above.\033[0m")
    print()


if __name__ == "__main__":
    main()
