"""
语音合成与克隆 API 路由
=======================

提供 TTS 语音合成、声音复刻等功能。
基于 Vidu API。
"""

import logging
import re
import time
import uuid
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user
from app.core.exceptions import PlatformError
from app.db.session import get_db
from app.models.user import User
from app.schemas.common import ApiResponse, success_response
from app.services import point_service as ps
from app.services.vidu_service import (
    DEFAULT_VOICE_ID,
    ViduService,
    get_preset_voices,
    get_vidu_service,
)
from app.utils.audio_utils import get_audio_duration

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/voice", tags=["Voice"])


# ========== 请求/响应模型 ==========


class TTSSynthesizeRequest(BaseModel):
    """TTS 合成请求"""

    text: str = Field(..., min_length=1, max_length=10000, description="待合成文本（≤10000字符）")
    voice_id: str | None = Field(None, description="音色 ID（默认：甜美女性）")
    speed: float = Field(default=1.0, ge=0.5, le=2.0, description="语速 0.5-2.0")
    voice_language: str = Field(default="zh", description="音色语种 (zh/en)")
    volume: int = Field(default=0, ge=0, le=10, description="音量 0-10（0=正常）")
    pitch: int = Field(default=0, ge=-12, le=12, description="音调 -12 到 12")


class VoiceCloneSubmitRequest(BaseModel):
    """声音复刻提交请求"""

    source_audio_url: str | None = Field(None, description="源音频 URL（5-30秒，mp3/wav，需公开可访问）")
    source_video_url: str | None = Field(None, description="源视频 URL（可选）")
    video_id: str | None = Field(None, description="历史作品ID（可选）")
    voice_name: str | None = Field(None, description="自定义音色名称（≤20字符）")


class VoiceCloneTaskResponse(BaseModel):
    """克隆任务响应"""

    task_id: str
    status: str
    voice_id: str | None = None
    trial_url: str | None = None
    error_message: str | None = None


class VoiceInfo(BaseModel):
    """音色信息"""

    voice_id: str
    name: str
    description: str = ""
    language: str = "zh"
    recommended: bool = False
    previewUrl: str | None = None


# ========== API 路由 ==========


@router.get("/voices", response_model=ApiResponse[list[VoiceInfo]])
async def list_voices(
    current_user: User = Depends(get_current_user),
):
    """
    获取可用音色列表

    返回预设的音色选项，用户可选择喜欢的音色进行 TTS 合成。
    """
    voices = get_preset_voices()
    return success_response(
        data=[VoiceInfo(**v) for v in voices],
        message="获取音色列表成功",
    )


@router.post("/synthesize", response_model=ApiResponse[dict])
async def synthesize_speech(
    request: TTSSynthesizeRequest,
    service: ViduService = Depends(get_vidu_service),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    同步 TTS 合成

    将文本转换为语音，返回音频 URL。
    适用于短文本（≤10000字符）。
    """
    # 宽松预检：余额为负或零时阻止，避免浪费第三方资源
    balance_info = await ps.get_user_balance(db, current_user.id)
    if balance_info["balance"] <= 0:
        raise HTTPException(status_code=402, detail="余额不足，请先充值")

    try:
        audio_url = await service.synthesize(
            text=request.text,
            voice_id=request.voice_id,
            speed=request.speed,
            volume=request.volume,
            pitch=request.pitch,
        )

        # 探测音频时长并扣费
        try:
            seconds = await get_audio_duration(audio_url)
            points = ps._calculate_cost("tts", {"seconds": seconds})
            await ps.consume(
                db,
                user_id=current_user.id,
                points=points,
                source_type="tts",
                source_id=f"tts_{current_user.id}_{int(time.time() * 1000)}",
                description="【配音合成】",
                duration=seconds,
                allow_negative=True,
            )
            await db.commit()
        except ValueError as e:
            if "积分不足" in str(e):
                raise HTTPException(status_code=402, detail=str(e))
            logger.error(f"[Voice] TTS 扣费失败: {e}")
        except Exception as e:
            logger.error(f"[Voice] TTS 扣费失败: {e}")

        return success_response(
            data={
                "audio_url": audio_url,
                "format": "mp3",
                "text": request.text,
                "voice_id": request.voice_id or DEFAULT_VOICE_ID,
                "consumed_points": points,
                "duration": seconds,
            },
            message="合成成功",
        )

    except HTTPException:
        raise
    except PlatformError:
        raise
    except Exception as e:
        logger.error(f"[Voice] TTS 合成失败: {e}")
        raise HTTPException(status_code=500, detail="语音合成失败，请稍后重试")



def _normalize_voice_id(name: str | None) -> str:
    """
    将用户输入的名称规范化为 Vidu 合法的 voice_id。

    Vidu 要求：8~256 字符，首字符必须是字母。
    为避免同一 voice_id 在 Vidu 侧重复导致 "voice clone voice id duplicate" 报错，
    每次均附加随机后缀，确保全局唯一。
    """
    # 固定后缀：下划线 + 6 位十六进制随机字符（7 字符）
    suffix = f"_{uuid.uuid4().hex[:6]}"
    suffix_len = len(suffix)

    if not name:
        base = f"vidu_{uuid.uuid4().hex[:8]}"
    else:
        # 只保留字母、数字、下划线
        base = re.sub(r"[^a-zA-Z0-9_]", "", name)

        # 确保首字符是字母
        if base and not base[0].isalpha():
            base = "v" + base
        elif not base:
            base = "voice"

    # 预留后缀长度，总长度不超过 256
    max_base_len = 256 - suffix_len
    if len(base) > max_base_len:
        base = base[:max_base_len]

    # 长度不足 8（含后缀），补足随机字符
    min_total = 8
    if len(base) + suffix_len < min_total:
        pad_len = min_total - len(base) - suffix_len
        base = base + uuid.uuid4().hex[:pad_len]

    return base + suffix


@router.post("/clone/submit", response_model=ApiResponse[VoiceCloneTaskResponse])
async def submit_clone_task(
    request: VoiceCloneSubmitRequest,
    service: ViduService = Depends(get_vidu_service),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    提交声音复刻任务（Vidu）

    Vidu 声音复刻是同步接口，直接返回结果。
    """
    # 前置积分检查
    required_points = ps._calculate_cost("voice_clone")
    check = await ps.check_balance(db, current_user.id, required_points)
    if not check["sufficient"]:
        raise HTTPException(
            status_code=402,
            detail=f"积分不足，需要 {required_points} 积分，当前余额 {check['balance']}",
        )

    try:
        voice_id = _normalize_voice_id(request.voice_name)
        result = await service.clone_voice(
            audio_url=request.source_audio_url or "",
            voice_id=voice_id,
        )

        # 扣费
        try:
            points = ps._calculate_cost("voice_clone")
            await ps.consume(
                db,
                user_id=current_user.id,
                points=points,
                source_type="voice_clone",
                source_id=f"voice_clone_{current_user.id}_{int(time.time() * 1000)}_{result.get('voice_id', 'unknown')}",
                description="【声音复刻】",
            )
            await db.commit()
        except ValueError as e:
            if "积分不足" in str(e):
                raise HTTPException(status_code=402, detail=str(e))
            logger.error(f"[Voice] 克隆扣费失败: {e}")
        except Exception as e:
            logger.error(f"[Voice] 克隆扣费失败: {e}")

        # Vidu 同步返回，状态直接为 succeeded
        return success_response(
            data=VoiceCloneTaskResponse(
                task_id=result.get("task_id", ""),
                status="succeeded",
                voice_id=result.get("voice_id"),
                trial_url=result.get("demo_audio"),
            ),
            message="克隆成功",
        )

    except HTTPException:
        raise
    except PlatformError:
        raise
    except ValueError as e:
        logger.error(f"[Voice] 提交克隆任务失败: {e}")
        raise HTTPException(status_code=500, detail=f"参数错误: {e}")
    except Exception as e:
        logger.error(f"[Voice] 提交克隆任务失败: {e}")
        raise HTTPException(status_code=500, detail=f"任务提交失败: {e}")


@router.get("/clone/query/{task_id}", response_model=ApiResponse[VoiceCloneTaskResponse])
async def query_clone_task(
    task_id: str,
    blocking: bool = False,
    current_user: User = Depends(get_current_user),
):
    """
    查询声音复刻任务状态（Vidu）

    Vidu 声音复刻是同步接口，此端点仅做兼容，直接返回成功状态。
    """
    return success_response(
        data=VoiceCloneTaskResponse(
            task_id=task_id,
            status="succeeded",
        ),
        message="克隆已完成",
    )


