import base64
import datetime
from typing import cast

from astrbot.api import logger
from pydantic import ValidationError

from ..bangumi_types import JsonObject
from .base import BaseBangumiService
from .contracts import (
    EpisodeItem,
    EpisodeListResponse,
    SearchSubjectItem,
    SearchSubjectsResponse,
    SubjectDetailsResponse,
)
from .schemas import Episode
from .types import ImageSize


class SubjectsService(BaseBangumiService):
    def __init__(self, *args: object, **kwargs: object) -> None:
        super().__init__(*args, **kwargs)

    async def search_subjects(
        self,
        keyword: str,
        limit: int = 5,
        offset: int = 0,
        subject_type: list[int] | None = None,
        subject_tags: list[str] | None = None,
    ) -> SearchSubjectsResponse:
        cache_key = f"search:{keyword}:{limit}"
        if cache_key in self.search_cache:
            return self.search_cache[cache_key]

        url = f"{self.base_url}/v0/search/subjects"
        filters: dict[str, object] = {}
        json_data: JsonObject = {
            "keyword": keyword,
            "limit": limit,
            "offset": offset,
            "filter": filters,
        }
        if subject_type is not None:
            filters["type"] = subject_type
        if subject_tags is not None:
            filters["tag"] = subject_tags
        data = await self._request(
            url,
            method="POST",
            json_data=json_data,
        )
        if isinstance(data, dict):
            raw_items = data.get("data")
            if isinstance(raw_items, list):
                normalized: SearchSubjectsResponse = {"data": []}
                for item in raw_items:
                    if isinstance(item, dict):
                        normalized["data"].append(cast(SearchSubjectItem, item))
                self.search_cache[cache_key] = cast(SearchSubjectsResponse, normalized)
                return normalized

        fallback: SearchSubjectsResponse = {"data": []}
        self.search_cache[cache_key] = fallback
        return fallback

    async def get_subject_details(self, subject_id: str) -> SubjectDetailsResponse:
        """
        获取条目的信息
        """
        url = f"{self.base_url}/v0/subjects/{subject_id}"
        data = await self._request(url)
        return cast(SubjectDetailsResponse, data if isinstance(data, dict) else {})

    async def get_subject_image(self, subject_id: str, size: ImageSize) -> bytes:
        """
        获取条目的图片原始二进制数据
        """
        url = f"{self.base_url}/v0/subjects/{subject_id}/image"
        params: JsonObject = {"type": size.value}
        return await self._request(url, params=params, is_json=False)

    async def get_subject_base64image(
        self, subject_id: str, size: ImageSize
    ) -> str | None:
        """
        获取条目的图片并转换为 Base64 编码的字符串
        """
        try:
            image_bytes = await self.get_subject_image(subject_id, size)
            if image_bytes:
                return base64.b64encode(image_bytes).decode("utf-8")
        except (ValueError, TypeError, RuntimeError) as e:
            logger.error(f"获取条目 {subject_id} 的 Base64 图片失败: {e}")
        return None

    async def get_subject_episodes(self, subject_id: int) -> EpisodeListResponse:
        """
        获取条目的剧集信息

        Args:
            subject_id: 条目的id
        Returns:
            data: 剧集信息
            total: 总集数
        """
        url = f"{self.base_url}/v0/episodes"
        params: JsonObject = {"subject_id": subject_id}
        data = await self._request(url, params=params)
        if isinstance(data, dict):
            raw_items = data.get("data")
            if isinstance(raw_items, list):
                normalized: EpisodeListResponse = {"data": []}
                for item in raw_items:
                    if isinstance(item, dict):
                        normalized["data"].append(cast(EpisodeItem, item))
                return normalized
        return {"data": []}

    async def get_latest_episode(self, subject_id: int) -> Episode | None:
        """
        从 episodes 数据中提取最新一集的信息。
        最新一集的定义：已播出且有互动（评论）的普通剧集。
        """
        episodes_data = await self.get_subject_episodes(subject_id)
        raw_list = episodes_data.get("data", [])
        if not raw_list:
            return None

        # 解析并校验数据
        episodes = self._parse_episodes(raw_list)

        # 获取今天的日期用于比较
        today = datetime.date.today()

        # 逆序查找：从最后一集向前找第一个符合条件的
        for episode in reversed(episodes):
            if episode.ep == 0:
                continue

            # 检查播出状态
            is_aired = True
            if episode.airdate:
                try:
                    episode_date = datetime.datetime.strptime(
                        episode.airdate, "%Y-%m-%d"
                    ).date()
                    is_aired = episode_date <= today
                except ValueError:
                    # 日期格式异常时，不因为日期判定为未播出
                    pass

            # 核心业务逻辑：已播出且有评论互动
            if is_aired and episode.comment > 0:
                return episode

        return None

    @staticmethod
    def _parse_episodes(raw_data: list[EpisodeItem]) -> list[Episode]:
        """
        辅助函数：将原始字典列表解析为 Episode 模型列表，自动过滤校验失败的数据。
        """
        parsed_episodes: list[Episode] = []
        for item in raw_data:
            try:
                parsed_episodes.append(Episode(**item))
            except ValidationError as e:
                logger.warning(f"解析剧集数据失败，已跳过: {e}, 原始数据: {item}")
        return parsed_episodes
