import json
from typing import Optional, List, Dict, Any, ClassVar

from pydantic import BaseModel, Field, ConfigDict, model_validator, field_validator

from app.schemas.media import OptionalMediaIdentityMixin
from app.schemas.types import MediaSource, MediaType


def compute_subscribe_completed_episode(subscribe: "Subscribe") -> Optional[int]:
    """
    计算订阅"已完成"集数派生值，仅用于响应填充，不入库。

    普通电视剧按 ``total_episode - lack_episode`` 计算；分集洗版按订阅目标范围内
    priority==100 的分集数量计算；全集洗版按整包准入基线是否达到 100 计算。
    """
    total_episode = subscribe.total_episode or 0
    if subscribe.type != MediaType.TV.value or not total_episode:
        return None

    start_episode = subscribe.start_episode or 1
    if not subscribe.best_version:
        lack = subscribe.lack_episode or 0
        return max(total_episode - lack, 0)

    if subscribe.best_version_full:
        completed_targets = max(total_episode - start_episode + 1, 0) \
            if subscribe.current_priority == 100 else 0
        return min(min(max(start_episode - 1, 0), total_episode) + completed_targets, total_episode)

    episode_priority = subscribe.episode_priority or {}
    if not episode_priority and subscribe.current_priority is not None:
        # 兼容只有整体优先级的洗版快照，响应派生值需与链路侧按集口径保持一致。
        episode_priority = {
            str(episode): int(subscribe.current_priority)
            for episode in range(start_episode, total_episode + 1)
        }
    priority_completed = sum(
        1
        for ep_key, priority in episode_priority.items()
        if str(ep_key).isdigit()
        and start_episode <= int(ep_key) <= total_episode
        and priority == 100
    )
    return min(max(start_episode - 1, 0), total_episode) + priority_completed


class Subscribe(OptionalMediaIdentityMixin, BaseModel):
    """订阅输入与响应模型，媒体身份必须为空对或完整有效对。"""

    # 公共创建和更新接口不得接收系统字段和运行事实；其余字段默认作为订阅输入透传。
    PUBLIC_WRITE_EXCLUDED_FIELDS: ClassVar[frozenset[str]] = frozenset({
        "id", "poster", "backdrop", "vote", "description", "lack_episode", "completed_episode",
        "note", "state", "last_update", "username", "current_priority", "episode_priority", "date",
        "current_audio_format", "current_bitrate", "current_bit_depth", "current_sample_rate",
    })

    id: Optional[int] = None
    # 订阅名称
    name: Optional[str] = None
    # 订阅年份
    year: Optional[str] = None
    # 订阅类型 电影/电视剧
    type: Optional[str] = None
    # 搜索关键字
    keyword: Optional[str] = None
    media_source: Optional[MediaSource] = None
    media_id: Optional[str] = None
    # 音乐实体类型：recording 单曲、album 专辑
    music_type: Optional[str] = None
    # 专辑预期总曲目数
    total_tracks: Optional[int] = None
    # 季号
    season: Optional[int] = None
    # 海报
    poster: Optional[str] = None
    # 背景图
    backdrop: Optional[str] = None
    # 评分
    vote: Optional[float] = 0.0
    # 描述
    description: Optional[str] = None
    # 过滤规则
    filter: Optional[str] = None
    # 包含
    include: Optional[str] = None
    # 排除
    exclude: Optional[str] = None
    # 质量
    quality: Optional[str] = None
    # 分辨率
    resolution: Optional[str] = None
    # 特效
    effect: Optional[str] = None
    # 音乐音质等级，可用 | 组合 hires/lossless/lossy
    audio_quality: Optional[str] = None
    # 音频格式正则，如 FLAC|ALAC
    audio_format: Optional[str] = None
    # 最低码率（bps）
    min_bitrate: Optional[int] = None
    # 最低位深（bit）
    min_bit_depth: Optional[int] = None
    # 最低采样率（Hz）
    min_sample_rate: Optional[int] = None
    # 总集数
    total_episode: Optional[int] = 0
    # 开始集数
    start_episode: Optional[int] = 0
    # 缺失集数
    lack_episode: Optional[int] = 0
    # 已完成集数
    completed_episode: Optional[int] = None
    # 附加信息
    note: Optional[List[int]] = None
    # 状态：N-新建， R-订阅中
    state: Optional[str] = None
    # 最后更新时间
    last_update: Optional[str] = None
    # 订阅用户
    username: Optional[str] = None
    # 订阅站点
    sites: Optional[List[int]] = Field(default_factory=list)
    # 下载器
    downloader: Optional[str] = None
    # 是否洗版
    best_version: Optional[int] = None
    # 是否只洗全集整包
    best_version_full: Optional[int] = None
    # 当前优先级
    current_priority: Optional[int] = None
    # 当前音乐版本格式
    current_audio_format: Optional[str] = None
    # 当前音乐版本码率（bps）
    current_bitrate: Optional[int] = None
    # 当前音乐版本位深（bit）
    current_bit_depth: Optional[int] = None
    # 当前音乐版本采样率（Hz）
    current_sample_rate: Optional[int] = None
    # 洗版时已下载剧集的优先级状态
    episode_priority: Optional[Dict[str, int]] = None
    # 保存路径
    save_path: Optional[str] = None
    # 是否使用 imdbid 搜索
    search_imdbid: Optional[int] = 0
    # 时间
    date: Optional[str] = None
    # 自定义识别词
    custom_words: Optional[str] = None
    # 自定义媒体类别
    media_category: Optional[str] = None
    # 过滤规则组
    filter_groups: Optional[List[str]] = Field(default_factory=list)
    # 剧集组
    episode_group: Optional[str] = None

    model_config = ConfigDict(from_attributes=True)

    @field_validator("note", mode="before")
    @classmethod
    def _normalize_legacy_note(cls, value: Any) -> Any:
        """
        兼容历史字符串型 note。

        2.0 时代旧代码对 JSON 列显式做了 ``json.dumps``，历史数据可能是一层
        或两层 JSON 编码的字符串（如 ``'[1, 2, 3]'``），不解析会触发响应
        校验 500；解析失败按空值处理，避免脏数据阻塞整个订阅列表接口。
        """
        if not isinstance(value, str):
            return value
        parsed = value
        while isinstance(parsed, str):
            try:
                parsed = json.loads(parsed)
            except (TypeError, ValueError):
                return None
        if isinstance(parsed, list):
            return [item for item in parsed if isinstance(item, int)]
        return None

    @model_validator(mode="before")
    @classmethod
    def _normalize_empty_strings(cls, data: Any) -> Any:
        """
        将前端清空输入框后残留的空字符串视为空值。

        音乐等媒体类型的 season、total_episode、episode_priority 等数值或容器字段
        在表单中常以空字符串提交，而 Pydantic 不会把空字符串自动转为 None，会直接抛出
        校验异常导致接口返回 422。这里把空字符串键移除，等价于该字段未提供，从而复用字段
        默认值（如 ``total_episode`` 回退为 0、``sites`` 回退为空列表）。媒体身份键保留为
        None，以便更新接口区分“未提交”与“显式清空完整身份对”。
        """
        if isinstance(data, dict):
            data = dict(data)
            for key, value in list(data.items()):
                if isinstance(value, str) and value == "":
                    if key in {"media_source", "media_id"}:
                        data[key] = None
                    else:
                        data.pop(key)
        return data

    @model_validator(mode="after")
    def _fill_completed_episode(self) -> "Subscribe":
        """
        填充 ``completed_episode`` 派生字段。电视剧订阅按 best_version 分支计算，
        电影或缺少 total_episode 时保持 None。
        """
        if self.completed_episode is not None:
            # 调用方显式提供过的值不覆盖
            return self
        self.completed_episode = compute_subscribe_completed_episode(self)
        return self

    def to_public_write_payload(self, *, exclude_unset: bool = False) -> Dict[str, Any]:
        """裁剪公共订阅写入字段，可仅保留更新请求显式提交的字段。"""
        return self.model_dump(
            exclude=self.PUBLIC_WRITE_EXCLUDED_FIELDS,
            exclude_unset=exclude_unset,
        )


class SubscribeShare(OptionalMediaIdentityMixin, BaseModel):
    """可供其他用户复用的订阅分享信息。"""

    # 分享ID
    id: Optional[int] = None
    # 订阅ID
    subscribe_id: Optional[int] = None
    # 分享标题
    share_title: Optional[str] = None
    # 分享说明
    share_comment: Optional[str] = None
    # 分享人
    share_user: Optional[str] = None
    # 分享人唯一ID
    share_uid: Optional[str] = None
    # 订阅名称
    name: Optional[str] = None
    # 订阅年份
    year: Optional[str] = None
    # 订阅类型 电影/电视剧
    type: Optional[str] = None
    # 搜索关键字
    keyword: Optional[str] = None
    media_source: Optional[MediaSource] = None
    media_id: Optional[str] = None
    # 音乐实体类型：recording 单曲、album 专辑
    music_type: Optional[str] = None
    # 专辑预期总曲目数
    total_tracks: Optional[int] = None
    # 季号
    season: Optional[int] = None
    # 海报
    poster: Optional[str] = None
    # 背景图
    backdrop: Optional[str] = None
    # 评分
    vote: Optional[float] = 0.0
    # 描述
    description: Optional[str] = None
    # 包含
    include: Optional[str] = None
    # 排除
    exclude: Optional[str] = None
    # 质量
    quality: Optional[str] = None
    # 分辨率
    resolution: Optional[str] = None
    # 特效
    effect: Optional[str] = None
    # 音乐音质等级
    audio_quality: Optional[str] = None
    # 音频格式
    audio_format: Optional[str] = None
    # 最低码率（bps）
    min_bitrate: Optional[int] = None
    # 最低位深（bit）
    min_bit_depth: Optional[int] = None
    # 最低采样率（Hz）
    min_sample_rate: Optional[int] = None
    # 总集数
    total_episode: Optional[int] = 0
    # 时间
    date: Optional[str] = None
    # 自定义识别词
    custom_words: Optional[str] = None
    # 自定义媒体类别
    media_category: Optional[str] = None
    # 自定义剧集组
    episode_group: Optional[str] = None
    # 复用人次
    count: Optional[int] = 0


class SubscribeShareStatistics(BaseModel):
    """单个用户的订阅分享数量与复用统计。"""

    # 分享人
    share_user: Optional[str] = None
    # 分享数量
    share_count: Optional[int] = 0
    # 总复用人次
    total_reuse_count: Optional[int] = 0


class SubscribeDownloadFileInfo(BaseModel):
    """订阅剧集关联的下载文件信息。"""

    # 种子名称
    torrent_title: Optional[str] = None
    # 站点名称
    site_name: Optional[str] = None
    # 下载器
    downloader: Optional[str] = None
    # hash
    hash: Optional[str] = None
    # 文件路径
    file_path: Optional[str] = None


class SubscribeLibraryFileInfo(BaseModel):
    """订阅剧集关联的媒体库文件信息。"""

    # 存储
    storage: Optional[str] = "local"
    # 文件路径
    file_path: Optional[str] = None
    # 媒体服务器名称
    server: Optional[str] = None
    # 媒体服务器类型：emby、jellyfin、plex 等
    server_type: Optional[str] = None
    # 媒体服务器条目 ID
    itemid: Optional[str] = None


class SubscribeEpisodeInfo(BaseModel):
    """订阅单集的元数据及关联文件。"""

    # 标题
    title: Optional[str] = None
    # 描述
    description: Optional[str] = None
    # 背景图
    backdrop: Optional[str] = None
    # 下载文件信息
    download: Optional[List[SubscribeDownloadFileInfo]] = Field(default_factory=list)
    # 媒体库文件信息
    library: Optional[List[SubscribeLibraryFileInfo]] = Field(default_factory=list)


class SubscrbieInfo(BaseModel):
    """订阅详情及按集号归组的文件信息。"""

    # 订阅信息
    subscribe: Optional[Subscribe] = None
    # 集信息 {集号: {download: 文件路径，library: 文件路径, backdrop: url, title: 标题, description: 描述}}
    episodes: Optional[Dict[int, SubscribeEpisodeInfo]] = Field(default_factory=dict)
