"""订阅存在性、来源定位和类型状态查询应用服务。"""

from __future__ import annotations

from typing import Any, Optional, Protocol

from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas.media import resolve_media_identity
from app.schemas.types import MediaType
from app.schemas.workflow import Subscribe as SubscribeView


class SubscriptionQueryRepository(Protocol):
    """描述订阅查询切片所需的最小仓储能力。"""

    def exists(self, **identity: Any) -> bool:
        """按完整订阅身份判断记录是否存在。"""
        ...

    def get_by(self, **identity: Any) -> Optional[Any]:
        """按来源关键字中的订阅身份读取单条记录。"""
        ...

    def list(self, state: Optional[str] = None) -> list[Any]:
        """按可选状态集合读取订阅记录。"""
        ...


class AsyncSubscriptionQueryRepository(Protocol):
    """公开订阅查询所需的异步持久化端口。"""

    async def async_list(self) -> list[Any]:
        """读取全部订阅。"""
        ...

    async def async_list_by_username(self, username: str) -> list[Any]:
        """读取指定用户订阅。"""
        ...

    async def async_get(self, subscribe_id: int) -> Optional[Any]:
        """按 ID 读取订阅。"""
        ...

    async def async_list_by_media_identity(
        self,
        media_source: Any,
        media_id: str,
        music_type: Optional[str] = None,
    ) -> list[Any]:
        """按规范媒体身份读取订阅。"""
        ...


class AsyncSubscriptionHistoryQueryRepository(Protocol):
    """订阅历史公开查询所需的异步持久化端口。"""

    async def async_list_by_type(
        self,
        mtype: str,
        page: int = 1,
        count: int = 30,
    ) -> list[Any]:
        """按媒体类型分页读取订阅历史。"""
        ...

    async def async_list_by_type_and_username(
        self,
        mtype: str,
        username: str,
        page: int = 1,
        count: int = 30,
    ) -> list[Any]:
        """按媒体类型和用户分页读取订阅历史。"""
        ...


class SubscriptionQueryService:
    """封装不修改订阅状态的三个公开查询用例。"""

    _SOURCE_FIELDS = {
        "type",
        "season",
        "media_source",
        "media_id",
        "music_type",
    }

    def __init__(
        self,
        repository: SubscriptionQueryRepository,
        *,
        async_repository: Optional[AsyncSubscriptionQueryRepository] = None,
        history_repository: Optional[AsyncSubscriptionHistoryQueryRepository] = None,
    ) -> None:
        """保存订阅查询仓储端口。"""
        self._repository = repository
        self._async_repository = async_repository
        self._history_repository = history_repository

    async def list_public(
        self,
        username: Optional[str] = None,
    ) -> list[SubscribeView]:
        """读取公开订阅列表并转换为稳定 DTO。"""
        if self._async_repository is None:
            raise RuntimeError("异步订阅查询端口未注册")
        if username:
            records = await self._async_repository.async_list_by_username(
                username=username
            )
        else:
            records = await self._async_repository.async_list()
        return [SubscribeView.model_validate(record) for record in records]

    async def get_public(self, subscribe_id: int) -> Optional[SubscribeView]:
        """按 ID 读取订阅 DTO。"""
        if self._async_repository is None:
            raise RuntimeError("异步订阅查询端口未注册")
        record = await self._async_repository.async_get(subscribe_id)
        return SubscribeView.model_validate(record) if record else None

    async def list_by_media_identity(
        self,
        media_source: Any,
        media_id: str,
        music_type: Optional[str] = None,
    ) -> list[SubscribeView]:
        """按媒体身份读取订阅 DTO，并兼容旧音乐记录。"""
        if self._async_repository is None:
            raise RuntimeError("异步订阅查询端口未注册")
        records = await self._async_repository.async_list_by_media_identity(
            media_source=media_source,
            media_id=media_id,
            music_type=music_type,
        )
        return [
            SubscribeView.model_validate(record)
            for record in records
            if self._matches_music_type(record, music_type)
        ]

    async def list_history(
        self,
        mtype: str,
        *,
        page: int = 1,
        count: int = 30,
        username: Optional[str] = None,
    ) -> list[SubscribeView]:
        """分页读取订阅历史 DTO。"""
        if self._history_repository is None:
            raise RuntimeError("订阅历史查询端口未注册")
        if username:
            records = await self._history_repository.async_list_by_type_and_username(
                mtype,
                username,
                page,
                count,
            )
        else:
            records = await self._history_repository.async_list_by_type(
                mtype,
                page,
                count,
            )
        result = []
        for record in records:
            item = SubscribeView.model_validate(record)
            if item.type == MediaType.TV.value:
                item.total_episode = 0
                item.lack_episode = 0
            result.append(item)
        return result

    @staticmethod
    def _matches_music_type(record: Any, music_type: Optional[str]) -> bool:
        """把迁移前未标注音乐类型的记录兼容为单曲。"""
        if not music_type:
            return True
        value = getattr(record, "music_type", None)
        return value == music_type or (
            music_type == "recording" and value is None
        )

    def exists(
        self,
        mediainfo: MediaInfo,
        meta: Optional[MetaBase] = None,
    ) -> bool:
        """按媒体身份、季、剧集组和音乐实体类型判断订阅是否存在。"""
        media_source, media_id = resolve_media_identity(media=mediainfo)
        return bool(self._repository.exists(
            media_source=media_source,
            media_id=media_id,
            music_type=getattr(mediainfo, "music_type", None)
            if mediainfo.type == MediaType.MUSIC else None,
            season=meta.begin_season if meta else None,
            episode_group=mediainfo.episode_group,
        ))

    def get_by_source(self, source_keyword: Optional[dict]) -> Optional[Any]:
        """从已解析来源关键字筛出稳定身份字段并读取订阅。"""
        if not source_keyword:
            return None
        identity = {
            key: value
            for key, value in source_keyword.items()
            if key in self._SOURCE_FIELDS
        }
        return self._repository.get_by(**identity)

    def has_music(self, searchable_states: str) -> bool:
        """判断给定可搜索状态内是否至少存在一个音乐订阅。"""
        return any(
            subscribe.type == MediaType.MUSIC.value
            for subscribe in self._repository.list(searchable_states) or []
        )
