"""VoiceCloneProfile 领域模型 — Phase 3 CosyVoice 集成.

音色克隆档案，用于管理用户的自定义音色。

状态机：
  pending → processing → ready
                  ↘ failed → pending (重试)
       ↘ disabled
"""

from __future__ import annotations

import sys
from dataclasses import dataclass, field
from datetime import datetime, timezone

if sys.version_info >= (3, 11):
    from enum import StrEnum
else:
    from enum import Enum

    class StrEnum(str, Enum):
        pass


from uuid import uuid4


class VoiceCloneStatus(StrEnum):
    """音色克隆状态枚举。"""

    PENDING = "pending"
    """待处理（音频已上传，等待克隆）"""

    PROCESSING = "processing"
    """处理中（正在调用 CosyVoice API 克隆）"""

    READY = "ready"
    """就绪（克隆完成，可用于 TTS）"""

    FAILED = "failed"
    """失败（克隆失败）"""

    DISABLED = "disabled"
    """已禁用（用户手动禁用或违规）"""


# 终态集合
TERMINAL_STATUSES = frozenset({VoiceCloneStatus.READY, VoiceCloneStatus.FAILED, VoiceCloneStatus.DISABLED})

# 合法状态转换
_VALID_TRANSITIONS: dict[VoiceCloneStatus, set[VoiceCloneStatus]] = {
    VoiceCloneStatus.PENDING: {VoiceCloneStatus.PROCESSING, VoiceCloneStatus.FAILED, VoiceCloneStatus.DISABLED},
    VoiceCloneStatus.PROCESSING: {VoiceCloneStatus.READY, VoiceCloneStatus.FAILED, VoiceCloneStatus.DISABLED},
    VoiceCloneStatus.FAILED: {VoiceCloneStatus.PENDING},  # 重试回到 pending
    VoiceCloneStatus.READY: {VoiceCloneStatus.DISABLED},  # 可以禁用已就绪的音色
}


@dataclass(slots=True)
class VoiceCloneProfile:
    """音色克隆档案实体。

    Attributes:
        id: 档案唯一标识
        user_id: 所属用户
        name: 音色名称（用户可见）
        description: 音色描述
        status: 当前状态
        source_audio_url: 源音频文件 URL（用户上传的用于克隆的音频）
        voice_id: CosyVoice 返回的音色 ID（克隆成功后填充）
        voice_model: 使用的模型名称
        language: 语言代码（如 zh-CN, en-US）
        gender: 性别（male/female/unknown）
        error_message: 错误信息
        retry_count: 已重试次数
        max_retries: 最大重试次数
        metadata: 扩展元数据（JSON）
        created_at: 创建时间
        updated_at: 最后更新时间
    """

    id: str
    user_id: str
    name: str
    description: str = ""
    status: VoiceCloneStatus = VoiceCloneStatus.PENDING
    source_audio_url: str = ""
    voice_id: str = ""
    voice_model: str = ""
    language: str = "zh-CN"
    gender: str = "unknown"
    error_message: str = ""
    retry_count: int = 0
    max_retries: int = 3
    metadata: dict = field(default_factory=dict)
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    @classmethod
    def create(
        cls,
        user_id: str,
        name: str,
        *,
        description: str = "",
        source_audio_url: str = "",
        voice_model: str = "",
        language: str = "zh-CN",
        gender: str = "unknown",
        max_retries: int = 3,
        metadata: dict | None = None,
    ) -> VoiceCloneProfile:
        """创建音色克隆档案。

        Args:
            user_id: 用户 ID
            name: 音色名称
            description: 音色描述
            source_audio_url: 源音频文件 URL
            voice_model: 使用的模型名称
            language: 语言代码
            gender: 性别
            max_retries: 最大重试次数
            metadata: 扩展元数据

        Returns:
            新建的 VoiceCloneProfile 实例

        Raises:
            ValueError: 参数校验失败
        """
        if not user_id.strip():
            raise ValueError("user_id 不能为空")
        if not name.strip():
            raise ValueError("name 不能为空")
        if len(name.strip()) > 100:
            raise ValueError("name 长度不能超过 100 字符")

        return cls(
            id=uuid4().hex,
            user_id=user_id.strip(),
            name=name.strip(),
            description=description.strip(),
            source_audio_url=source_audio_url.strip(),
            voice_model=voice_model.strip(),
            language=language.strip(),
            gender=gender.strip().lower(),
            max_retries=max_retries,
            metadata=metadata or {},
        )

    @property
    def is_terminal(self) -> bool:
        """是否处于终态。"""
        return self.status in TERMINAL_STATUSES

    @property
    def is_retryable(self) -> bool:
        """是否可重试（失败且未超过重试上限）。"""
        return self.status == VoiceCloneStatus.FAILED and self.retry_count < self.max_retries

    @property
    def is_ready(self) -> bool:
        """是否就绪（可用于 TTS）。"""
        return self.status == VoiceCloneStatus.READY and bool(self.voice_id)

    def transition_to(self, new_status: VoiceCloneStatus | str) -> None:
        """执行状态转换。

        Args:
            new_status: 目标状态

        Raises:
            ValueError: 非法状态转换
        """
        if isinstance(new_status, str):
            try:
                new_status = VoiceCloneStatus(new_status)
            except ValueError:
                raise ValueError(f"无效状态: {new_status}")

        allowed = _VALID_TRANSITIONS.get(self.status, set())
        if new_status not in allowed:
            raise ValueError(
                f"非法状态转换: {self.status.value} → {new_status.value}，"
                f"允许: {{{', '.join(s.value for s in allowed)}}}"
            )

        now = datetime.now(timezone.utc)
        self.status = new_status
        self.updated_at = now

    def mark_processing(self) -> None:
        """标记为处理中。"""
        self.transition_to(VoiceCloneStatus.PROCESSING)
        self.error_message = ""

    def mark_ready(self, voice_id: str) -> None:
        """标记为就绪。

        Args:
            voice_id: CosyVoice 返回的音色 ID
        """
        if not voice_id.strip():
            raise ValueError("voice_id 不能为空")
        self.transition_to(VoiceCloneStatus.READY)
        self.voice_id = voice_id.strip()
        self.error_message = ""

    def mark_failed(self, error_message: str) -> None:
        """标记为失败。

        Args:
            error_message: 错误信息
        """
        self.transition_to(VoiceCloneStatus.FAILED)
        self.error_message = error_message

    def mark_disabled(self) -> None:
        """标记为禁用。"""
        self.transition_to(VoiceCloneStatus.DISABLED)

    def prepare_retry(self) -> None:
        """准备重试：重置状态为 pending。

        Raises:
            ValueError: 不可重试
        """
        if not self.is_retryable:
            raise ValueError(
                f"音色克隆不可重试: status={self.status.value}, "
                f"retry_count={self.retry_count}, max_retries={self.max_retries}"
            )
        self.retry_count += 1
        self.transition_to(VoiceCloneStatus.PENDING)
        self.error_message = ""
        self.voice_id = ""

    def to_dict(self) -> dict:
        """序列化为字典。"""
        return {
            "id": self.id,
            "user_id": self.user_id,
            "name": self.name,
            "description": self.description,
            "status": self.status.value,
            "source_audio_url": self.source_audio_url,
            "voice_id": self.voice_id,
            "voice_model": self.voice_model,
            "language": self.language,
            "gender": self.gender,
            "error_message": self.error_message,
            "retry_count": self.retry_count,
            "max_retries": self.max_retries,
            "is_retryable": self.is_retryable,
            "is_ready": self.is_ready,
            "metadata": self.metadata,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }
