import asyncio
import logging
import uuid
from typing import Dict, Any, Optional, Tuple

import httpx

from src.services.provider.base import BaseAudioService, ServiceResponse, TaskStatus, GenerationResult
from src.services.storage_service import storage_manager
from src.config.settings import MINIMAX_GROUP_ID

logger = logging.getLogger(__name__)


class MiniMaxAudioService(BaseAudioService):
    BASE_URL = "https://api.minimaxi.com/v1"

    def __init__(self, model_name: str = "speech-2.8-hd", api_key: Optional[str] = None, **kwargs):
        super().__init__(model_name, api_key=api_key, **kwargs)
        if not self.api_key:
            logger.warning("API Key is not set. Audio generation will fail.")

    def _build_headers(self, group_id: Optional[str] = None) -> Dict[str, str]:
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_key}",
        }
        group_id = group_id or MINIMAX_GROUP_ID
        if group_id:
            headers["x-minimax-group-id"] = group_id
        return headers

    async def _request_json_async(self, method: str, url: str, headers: Dict[str, str], **kwargs) -> Dict[str, Any]:
        async with httpx.AsyncClient(timeout=httpx.Timeout(30.0, read=120.0)) as client:
            response = await client.request(method, url, headers=headers, **kwargs)
            if response.status_code != 200:
                logger.error(f"MiniMax request failed: {response.status_code} - {response.text}")
                response.raise_for_status()
            return response.json()

    @staticmethod
    def _ensure_success(data: Dict[str, Any], context: str) -> None:
        base_resp = data.get("base_resp", {})
        status_code = base_resp.get("status_code")
        if status_code is not None and status_code != 0:
            status_msg = base_resp.get("status_msg", "unknown error")
            raise RuntimeError(f"MiniMax {context} failed: {status_code} {status_msg}")

    @staticmethod
    def _get_status_code(data: Dict[str, Any]) -> Optional[int]:
        base_resp = data.get("base_resp", {})
        code = base_resp.get("status_code")
        return int(code) if code is not None else None

    @staticmethod
    def _extract_task_id(data: Dict[str, Any]) -> Optional[str]:
        candidates = [
            data.get("task_id"),
            data.get("data", {}).get("task_id"),
            data.get("id"),
            data.get("data", {}).get("id"),
        ]
        for c in candidates:
            if c:
                return str(c)
        return None

    @staticmethod
    def _extract_query_fields(data: Dict[str, Any]) -> Tuple[str, Optional[str], Optional[str], Optional[Dict[str, Any]]]:
        payload = data.get("data", {}) if isinstance(data.get("data"), dict) else {}
        status = (
            payload.get("status")
            or data.get("status")
            or payload.get("task_status")
            or data.get("task_status")
            or "unknown"
        )
        file_id = payload.get("file_id") or data.get("file_id")
        download_url = (
            payload.get("download_url")
            or payload.get("audio_url")
            or payload.get("url")
            or data.get("download_url")
            or data.get("audio_url")
            or data.get("url")
        )
        extra_info = payload.get("extra_info") if isinstance(payload.get("extra_info"), dict) else data.get("extra_info")
        return str(status).lower(), (str(file_id) if file_id else None), (str(download_url) if download_url else None), extra_info

    async def generate(self, text: str, voice: str = "male-qn-qingse", **kwargs) -> ServiceResponse:
        """Generate audio using MiniMax async TTS API."""
        if not text or not text.strip():
            raise RuntimeError("Text cannot be empty")

        fmt = kwargs.get("response_format") or kwargs.get("format") or "mp3"
        headers = self._build_headers(kwargs.get("group_id"))

        # 1) Create async task
        create_url = f"{self.BASE_URL}/t2a_async_v2"
        base_payload: Dict[str, Any] = {
            "model": self.model_name,
            "language_boost": kwargs.get("language_boost", "auto"),
            "voice_setting": {
                "voice_id": voice or "male-qn-qingse",
                "speed": float(kwargs.get("speed", 1.0)),
                "vol": float(kwargs.get("vol", 1.0)),
                "pitch": float(kwargs.get("pitch", 0)),
            },
            "audio_setting": {
                "audio_sample_rate": int(kwargs.get("audio_sample_rate", kwargs.get("sample_rate", 32000))),
                "bitrate": int(kwargs.get("bitrate", 128000)),
                "format": fmt,
                "channel": int(kwargs.get("channel", 1)),
            },
        }

        text_file_id = kwargs.get("text_file_id")
        if text_file_id:
            base_payload["text_file_id"] = text_file_id
        else:
            base_payload["text"] = text

        if isinstance(kwargs.get("pronunciation_dict"), dict):
            base_payload["pronunciation_dict"] = kwargs["pronunciation_dict"]
        if isinstance(kwargs.get("voice_modify"), dict):
            base_payload["voice_modify"] = kwargs["voice_modify"]

        try:
            logger.info(f"MiniMax TTS: model={self.model_name}, voice={voice}, text_len={len(text)}")
            create_data = await self._request_json_async(
                "POST",
                create_url,
                headers,
                json=base_payload,
            )
            status_code = self._get_status_code(create_data)
            if status_code == 2013:
                # Fallback 1: remove optional fields and keep only core params
                fallback_payload = {
                    "model": self.model_name,
                    "text": text,
                    "voice_setting": {
                        "voice_id": voice or "male-qn-qingse",
                        "speed": 1.0,
                        "vol": 1.0,
                        "pitch": 0,
                    },
                    "audio_setting": {
                        "audio_sample_rate": 32000,
                        "bitrate": 128000,
                        "format": fmt,
                        "channel": 1,
                    },
                }
                logger.warning(
                    f"MiniMax create task returned 2013, retrying with fallback payload: model={self.model_name}, voice={fallback_payload['voice_setting']['voice_id']}, format={fmt}"
                )
                create_data = await self._request_json_async(
                    "POST",
                    create_url,
                    headers,
                    json=fallback_payload,
                )
                status_code = self._get_status_code(create_data)

            if status_code == 2013:
                # Fallback 2: switch audio_sample_rate -> sample_rate for compatibility
                fallback_payload_v2 = {
                    "model": self.model_name,
                    "text": text,
                    "voice_setting": {
                        "voice_id": voice or "male-qn-qingse",
                        "speed": 1.0,
                        "vol": 1.0,
                        "pitch": 0,
                    },
                    "audio_setting": {
                        "sample_rate": 32000,
                        "bitrate": 128000,
                        "format": fmt,
                        "channel": 1,
                    },
                }
                logger.warning(
                    f"MiniMax create task still 2013, retrying with sample_rate compatibility payload: model={self.model_name}"
                )
                create_data = await self._request_json_async(
                    "POST",
                    create_url,
                    headers,
                    json=fallback_payload_v2,
                )
                status_code = self._get_status_code(create_data)

            if status_code == 2013:
                # Fallback 3: use official sample voice_id for broader compatibility
                fallback_payload_v3 = {
                    "model": self.model_name,
                    "text": text,
                    "voice_setting": {
                        "voice_id": "audiobook_male_1",
                        "speed": 1.0,
                        "vol": 1.0,
                        "pitch": 0,
                    },
                    "audio_setting": {
                        "audio_sample_rate": 32000,
                        "bitrate": 128000,
                        "format": fmt,
                        "channel": 1,
                    },
                }
                logger.warning(
                    "MiniMax create task still 2013, retrying with official sample voice_id=audiobook_male_1"
                )
                create_data = await self._request_json_async(
                    "POST",
                    create_url,
                    headers,
                    json=fallback_payload_v3,
                )

            self._ensure_success(create_data, "create task")

            task_id = self._extract_task_id(create_data)
            if not task_id:
                raise RuntimeError(f"No task_id in MiniMax response: {create_data}")

            # 2) Poll task
            query_url = f"{self.BASE_URL}/query/t2a_async_query_v2?task_id={task_id}"
            poll_interval = float(kwargs.get("poll_interval", 2.0))
            timeout_seconds = int(kwargs.get("timeout_seconds", 180))
            deadline = asyncio.get_event_loop().time() + timeout_seconds

            file_id: Optional[str] = None
            download_url: Optional[str] = None
            extra_info: Optional[Dict[str, Any]] = None

            while asyncio.get_event_loop().time() < deadline:
                query_data = await self._request_json_async(
                    "GET",
                    query_url,
                    headers,
                )
                self._ensure_success(query_data, "query task")
                status, file_id, download_url, extra_info = self._extract_query_fields(query_data)

                if status in ("succeed", "success", "succeeded", "done", "completed"):
                    break
                if status in ("fail", "failed", "error"):
                    raise RuntimeError(f"MiniMax task failed: {query_data}")

                await asyncio.sleep(poll_interval)
            else:
                raise RuntimeError(f"MiniMax task polling timeout after {timeout_seconds}s")

            # 3) Download audio content
            if file_id:
                retrieve_url = f"{self.BASE_URL}/files/retrieve_content?file_id={file_id}"
                async with httpx.AsyncClient(timeout=httpx.Timeout(30.0, read=120.0)) as client:
                    response = await client.get(retrieve_url, headers=headers)
                if response.status_code != 200:
                    logger.error(f"MiniMax retrieve_content failed: {response.status_code} - {response.text}")
                    response.raise_for_status()
                audio_bytes = response.content
            elif download_url:
                async with httpx.AsyncClient(timeout=httpx.Timeout(30.0, read=120.0)) as client:
                    response = await client.get(download_url)
                if response.status_code != 200:
                    logger.error(f"MiniMax download URL failed: {response.status_code} - {response.text}")
                    response.raise_for_status()
                audio_bytes = response.content
            else:
                raise RuntimeError("MiniMax task succeeded but no file_id/download_url returned")

            file_name = f"{uuid.uuid4()}.{fmt}"
            storage_path = f"generations/audio/{task_id}/{file_name}"
            saved_url = storage_manager.save(storage_path, audio_bytes)

            return ServiceResponse(
                task_id=task_id,
                status=TaskStatus.SUCCEEDED,
                results=[GenerationResult(url=saved_url)],
                meta={
                    "duration": (extra_info or {}).get("audio_length"),
                    "audio_size": (extra_info or {}).get("audio_size"),
                    "file_id": file_id,
                    "provider_task_id": task_id,
                },
            )
            
        except Exception as e:
            logger.error(f"MiniMax TTS Failed: {str(e)}")
            raise RuntimeError(f"MiniMax TTS Service Error: {str(e)}")
