"""Audio/video transcription tool."""

from __future__ import annotations

import asyncio
import json
import os
import shutil
import tempfile
import time
from pathlib import Path
from typing import Any

import httpx
from loguru import logger

from madpod_agent.agent.tools.base import Tool

_AUDIO_EXTENSIONS = frozenset({".mp3", ".wav", ".flac", ".ogg", ".m4a", ".aac", ".wma", ".opus", ".webm"})
_VIDEO_EXTENSIONS = frozenset({".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v", ".ts"})
_MAX_AUDIO_SIZE = 100 * 1024 * 1024  # 100 MB (Groq/OpenAI limit ~25MB, but we allow larger for local extraction)
_TRANSCRIPTION_TIMEOUT = 180.0


def _resolve_safe(path_str: str, workspace: Path, allowed_dir: Path | None) -> Path:
    p = Path(path_str).expanduser()
    if not p.is_absolute():
        p = workspace / p
    resolved = p.resolve()
    if allowed_dir:
        try:
            resolved.relative_to(allowed_dir.resolve())
        except ValueError:
            raise PermissionError(f"Path {path_str} is outside allowed directory")
    return resolved


async def _extract_audio_from_video(video_path: Path, output_path: Path) -> str | None:
    """Extract audio track from video using ffmpeg. Returns error string or None."""
    if not shutil.which("ffmpeg"):
        return "ffmpeg not installed — cannot extract audio from video"

    proc = await asyncio.create_subprocess_exec(
        "ffmpeg", "-i", str(video_path),
        "-vn", "-acodec", "libmp3lame", "-q:a", "4",
        "-y", str(output_path),
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    try:
        _, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
    except asyncio.TimeoutError:
        proc.kill()
        return "ffmpeg timed out extracting audio"

    if proc.returncode != 0:
        return f"ffmpeg error: {stderr.decode(errors='replace')[:300]}"
    return None


async def _get_media_duration(path: Path) -> float | None:
    """Get duration in seconds using ffprobe."""
    if not shutil.which("ffprobe"):
        return None
    try:
        proc = await asyncio.create_subprocess_exec(
            "ffprobe", "-v", "quiet", "-print_format", "json",
            "-show_format", str(path),
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=10)
        data = json.loads(stdout.decode())
        return float(data.get("format", {}).get("duration", 0))
    except Exception:
        return None


class TranscribeTool(Tool):
    """Transcribe audio and video files to text."""

    def __init__(
        self,
        workspace: Path,
        allowed_dir: Path | None = None,
        groq_api_key: str | None = None,
        openai_api_key: str | None = None,
    ):
        self._workspace = workspace
        self._allowed_dir = allowed_dir
        self._groq_key = groq_api_key or os.environ.get("GROQ_API_KEY", "")
        self._openai_key = openai_api_key or os.environ.get("OPENAI_API_KEY", "")

    @property
    def name(self) -> str:
        return "transcribe"

    @property
    def description(self) -> str:
        return (
            "Transcribe audio or video files to text using Whisper. "
            "Supports MP3, WAV, FLAC, M4A, OGG, MP4, MKV, AVI, MOV, and more. "
            "For video files, the audio track is automatically extracted first. "
            "Use this for meeting recordings, interviews, podcasts, video content, or any audio."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to the audio or video file"},
                "language": {
                    "type": "string",
                    "description": "Optional language hint (ISO 639-1 code, e.g. 'en', 'es', 'fr')",
                },
                "provider": {
                    "type": "string",
                    "enum": ["auto", "groq", "openai"],
                    "description": "Transcription provider (default: auto — tries Groq then OpenAI)",
                },
            },
            "required": ["path"],
        }

    async def execute(
        self, path: str, language: str | None = None, provider: str = "auto", **kwargs: Any
    ) -> str:
        try:
            file_path = _resolve_safe(path, self._workspace, self._allowed_dir)
        except PermissionError as exc:
            return json.dumps({"error": str(exc)})

        if not file_path.exists():
            return json.dumps({"error": f"File not found: {path}"})

        suffix = file_path.suffix.lower()
        is_video = suffix in _VIDEO_EXTENSIONS
        is_audio = suffix in _AUDIO_EXTENSIONS

        if not is_video and not is_audio:
            return json.dumps({
                "error": f"Unsupported format: {suffix}. Supported: {', '.join(sorted(_AUDIO_EXTENSIONS | _VIDEO_EXTENSIONS))}",
            })

        # Get duration info
        duration = await _get_media_duration(file_path)

        # Extract audio from video if needed
        audio_path = file_path
        temp_audio: Path | None = None
        if is_video:
            temp_audio = Path(tempfile.mktemp(suffix=".mp3"))
            error = await _extract_audio_from_video(file_path, temp_audio)
            if error:
                temp_audio.unlink(missing_ok=True)
                return json.dumps({"error": error})
            audio_path = temp_audio

        try:
            file_size = audio_path.stat().st_size
            if file_size > _MAX_AUDIO_SIZE:
                return json.dumps({
                    "error": f"Audio too large: {file_size:,} bytes (max {_MAX_AUDIO_SIZE:,})",
                })

            # Transcribe
            text, used_provider, error = await self._transcribe(audio_path, language, provider)
            if error:
                return json.dumps({"error": error})

            return json.dumps({
                "ok": True,
                "path": str(file_path),
                "duration_seconds": round(duration, 1) if duration else None,
                "provider": used_provider,
                "language": language,
                "chars": len(text),
                "text": text,
            })
        finally:
            if temp_audio:
                temp_audio.unlink(missing_ok=True)

    async def _transcribe(
        self, audio_path: Path, language: str | None, provider: str
    ) -> tuple[str, str, str | None]:
        """Try transcription providers in order. Returns (text, provider_name, error)."""
        providers = []
        if provider in ("auto", "groq") and self._groq_key:
            providers.append(("groq", self._transcribe_groq))
        if provider in ("auto", "openai") and self._openai_key:
            providers.append(("openai", self._transcribe_openai))

        if not providers:
            return "", "", "No transcription API key configured. Set GROQ_API_KEY or OPENAI_API_KEY."

        for name, fn in providers:
            try:
                text = await fn(audio_path, language)
                if text:
                    return text, name, None
            except Exception as exc:
                logger.warning("Transcription with {} failed: {}", name, exc)
                if name == providers[-1][0]:
                    return "", name, str(exc)

        return "", "", "All transcription providers returned empty text"

    async def _transcribe_groq(self, audio_path: Path, language: str | None) -> str:
        async with httpx.AsyncClient(timeout=_TRANSCRIPTION_TIMEOUT) as client:
            with open(audio_path, "rb") as f:
                data = {"model": "whisper-large-v3"}
                if language:
                    data["language"] = language
                files = {"file": (audio_path.name, f, "audio/mpeg")}
                response = await client.post(
                    "https://api.groq.com/openai/v1/audio/transcriptions",
                    headers={"Authorization": f"Bearer {self._groq_key}"},
                    data=data,
                    files=files,
                )
                response.raise_for_status()
                return response.json().get("text", "")

    async def _transcribe_openai(self, audio_path: Path, language: str | None) -> str:
        async with httpx.AsyncClient(timeout=_TRANSCRIPTION_TIMEOUT) as client:
            with open(audio_path, "rb") as f:
                data = {"model": "whisper-1"}
                if language:
                    data["language"] = language
                files = {"file": (audio_path.name, f, "audio/mpeg")}
                response = await client.post(
                    "https://api.openai.com/v1/audio/transcriptions",
                    headers={"Authorization": f"Bearer {self._openai_key}"},
                    data=data,
                    files=files,
                )
                response.raise_for_status()
                return response.json().get("text", "")
