"""MCP server that makes AI agents speak a brief summary of every response."""

import argparse
import hashlib
import logging
import os
import shutil
import subprocess
import time
from pathlib import Path

import httpx
import platformdirs
from mcp.server.fastmcp import FastMCP

# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
log = logging.getLogger("mcp-speak-when-done")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")

# ---------------------------------------------------------------------------
# Provider detection & configuration
# ---------------------------------------------------------------------------
CACHE_MAX_AGE = 24 * 3600  # seconds


def _detect_provider(api_key: str) -> dict:
    """Return sensible defaults based on API key prefix."""
    if api_key.startswith("gsk_"):
        return {
            "model": "canopylabs/orpheus-v1-english",
            "voice": "troy",
            "api_url": "https://api.groq.com/openai/v1/audio/speech",
        }
    return {
        "model": "tts-1",
        "voice": "alloy",
        "api_url": "https://api.openai.com/v1/audio/speech",
    }


def _load_config() -> dict:
    """Load configuration from env vars with smart provider defaults."""
    api_key = os.environ.get("OPENAI_API_KEY", "")
    provider = _detect_provider(api_key)
    return {
        "api_key": api_key,
        "api_url": os.environ.get("SPEECH_API_URL", provider["api_url"]),
        "voice": os.environ.get("SPEECH_VOICE", provider["voice"]),
        "speed": float(os.environ.get("SPEECH_SPEED", "1.0")),
        "model": os.environ.get("SPEECH_MODEL", provider["model"]),
        "max_retries": int(os.environ.get("SPEECH_MAX_RETRIES", "3")),
        "timeout": int(os.environ.get("SPEECH_TIMEOUT", "30")),
        "proxy_url": os.environ.get("SPEECH_PROXY_URL", ""),
        "proxy_timeout": int(os.environ.get("SPEECH_PROXY_TIMEOUT", "10")),
    }


# ---------------------------------------------------------------------------
# Cache
# ---------------------------------------------------------------------------
CACHE_DIR = Path(platformdirs.user_cache_dir("mcp-speak-when-done"))
CACHE_DIR.mkdir(parents=True, exist_ok=True)


def _cleanup_old_files() -> int:
    """Remove cached audio files older than CACHE_MAX_AGE. Returns count removed."""
    count = 0
    cutoff = time.time() - CACHE_MAX_AGE
    for f in CACHE_DIR.glob("speech_*.wav"):
        try:
            if f.stat().st_mtime < cutoff:
                f.unlink(missing_ok=True)
                count += 1
        except OSError:
            pass
    if count:
        log.info("Cleaned up %d old audio files", count)
    return count


def _cache_path(text: str, voice: str, speed: float, model: str) -> Path:
    """Return a deterministic cache path based on input parameters."""
    key = f"{text} {voice} {speed} {model}"
    h = hashlib.sha256(key.encode()).hexdigest()[:12]
    return CACHE_DIR / f"speech_{h}.wav"


# ---------------------------------------------------------------------------
# TTS
# ---------------------------------------------------------------------------


def _generate_speech(text: str, voice: str, speed: float, cfg: dict) -> Path:
    """Call the TTS API (with retries) and return path to the audio file."""
    path = _cache_path(text, voice, speed, cfg["model"])
    if path.exists():
        log.info("Cache hit: %s", path.name)
        return path

    payload = {
        "model": cfg["model"],
        "input": text,
        "voice": voice,
        "speed": speed,
        "response_format": "wav",
    }
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {cfg['api_key']}",
    }

    last_error = None
    for attempt in range(cfg["max_retries"] + 1):
        if attempt > 0:
            wait = min(2 ** attempt, 8)
            log.info("Retry %d/%d in %ds…", attempt, cfg["max_retries"], wait)
            time.sleep(wait)
        try:
            log.info("API request attempt %d for: %s", attempt, text[:60])
            with httpx.Client(timeout=cfg["timeout"]) as client:
                resp = client.post(cfg["api_url"], json=payload, headers=headers)

            if resp.headers.get("content-type", "").startswith("application/json"):
                error_msg = resp.json().get("error", {}).get("message", resp.text)
                raise RuntimeError(f"API error: {error_msg}")

            resp.raise_for_status()
            path.write_bytes(resp.content)
            log.info("Audio saved: %s (%d bytes)", path.name, len(resp.content))
            return path

        except Exception as exc:
            last_error = exc
            log.warning("Attempt %d failed: %s", attempt, exc)

    raise RuntimeError(
        f"API call failed after {cfg['max_retries'] + 1} attempts: {last_error}"
    )


# ---------------------------------------------------------------------------
# Audio playback
# ---------------------------------------------------------------------------


def _find_player() -> str:
    """Find an available audio player."""
    for player in ("ffplay", "mplayer", "vlc"):
        if shutil.which(player):
            return player
    raise RuntimeError("No audio player found (tried ffplay, mplayer, vlc)")


def _play_audio(path: Path) -> None:
    """Play an audio file using the best available player."""
    player = _find_player()
    log.info("Playing %s with %s", path.name, player)

    if player == "ffplay":
        subprocess.run(
            ["ffplay", "-autoexit", "-nodisp", "-loglevel", "quiet", str(path)],
            stdin=subprocess.DEVNULL,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
    elif player == "mplayer":
        subprocess.run(
            ["mplayer", "-quiet", "-really-quiet", str(path)],
            stdin=subprocess.DEVNULL,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
    elif player == "vlc":
        subprocess.run(["open", str(path), "-a", "/Applications/VLC.app"])


# ---------------------------------------------------------------------------
# Proxy mode
# ---------------------------------------------------------------------------


async def _proxy_speak(text: str, voice: str, speed: float, cfg: dict) -> str:
    """Forward the speak request to a remote MCP server over SSE."""
    from mcp.client.session import ClientSession
    from mcp.client.sse import sse_client

    try:
        async with sse_client(cfg["proxy_url"], timeout=cfg["proxy_timeout"]) as (read, write):
            async with ClientSession(read, write) as session:
                await session.initialize()
                result = await session.call_tool(
                    "speak_when_done",
                    arguments={"text": text, "voice": voice, "speed": speed},
                )
                return result.content[0].text if result.content else "Done"
    except Exception as exc:
        log.warning("Proxy call failed (will return Done silently): %s", exc)
        return "Done"


# ---------------------------------------------------------------------------
# MCP Server
# ---------------------------------------------------------------------------

TOOL_DESCRIPTION = (
    "Speak a brief summary of your response out loud. "
    "Call this at the end of EVERY response. "
    "Summarize what you said in 1-3 short sentences — shorter is better. "
    "If your response is a question, speak the question only, not the suggested answers. "
    "Keep it natural and conversational."
)


def main():
    """Entry point for the mcp-speak-when-done command."""
    parser = argparse.ArgumentParser(description="MCP speak-when-done server")
    parser.add_argument(
        "--transport",
        choices=["stdio", "sse"],
        default="stdio",
        help="MCP transport (default: stdio)",
    )
    args = parser.parse_args()

    cfg = _load_config()
    mcp = FastMCP("speak-when-done")

    @mcp.tool(description=TOOL_DESCRIPTION)
    async def speak_when_done(
        text: str,
        voice: str = cfg["voice"],
        speed: float = cfg["speed"],
    ) -> str:
        try:
            if cfg["proxy_url"]:
                return await _proxy_speak(text, voice, speed, cfg)
            _cleanup_old_files()
            path = _generate_speech(text, voice, speed, cfg)
            _play_audio(path)
        except Exception as exc:
            log.error("speak_when_done failed: %s", exc)
        return "Done"

    mcp.run(transport=args.transport)


if __name__ == "__main__":
    main()
