from pathlib import Path
from typing import Optional, Tuple
from xml.dom import minidom

from app.core.config import settings
from app.core.context import MediaInfo
from app.core.meta import MetaBase
from app.schemas.types import MediaType
from app.utils.dom import DomUtils
from app.modules.themoviedb.tmdbapi import TmdbApi


class TmdbScraper:
    _meta_tmdb = None
    _img_tmdb = None

    @property
    def default_tmdb(self):
        """
        获取元数据TMDB Api
        """
        if not self._meta_tmdb:
            self._meta_tmdb = TmdbApi(language=settings.TMDB_LOCALE)
        return self._meta_tmdb

    def original_tmdb(self, mediainfo: Optional[MediaInfo] = None):
        """
        获取图片TMDB Api
        """
        if settings.TMDB_SCRAP_ORIGINAL_IMAGE and mediainfo:
            return TmdbApi(language=mediainfo.original_language)
        return self.default_tmdb

    def get_metadata_nfo(
        self,
        meta: MetaBase,
        mediainfo: MediaInfo,
        season: Optional[int] = None,
        episode: Optional[int] = None,
    ) -> Optional[str]:
        """
        获取NFO文件内容文本
        :param meta: 元数据
        :param mediainfo: 媒体信息
        :param season: 季号
        :param episode: 集号
        """
        if mediainfo.type == MediaType.MOVIE:
            # 电影元数据文件
            doc = self.__gen_movie_nfo_file(mediainfo=mediainfo)
        else:
            if season is not None:
                # 查询季信息
                if mediainfo.episode_group:
                    seasoninfo = self.default_tmdb.get_tv_group_detail(
                        mediainfo.episode_group, season=season
                    )
                else:
                    seasoninfo = self.default_tmdb.get_tv_season_detail(
                        mediainfo.tmdb_id, season=season
                    )
                if episode:
                    # 集元数据文件
                    episodeinfo = self.__get_episode_detail(
                        seasoninfo, meta.begin_episode
                    )
                    doc = self.__gen_tv_episode_nfo_file(
                        episodeinfo=episodeinfo,
                        tmdbid=mediainfo.tmdb_id,
                        season=season,
                        episode=episode,
                    )
                else:
                    # 季元数据文件
                    doc = self.__gen_tv_season_nfo_file(
                        seasoninfo=seasoninfo, season=season
                    )
            else:
                # 电视剧元数据文件
                doc = self.__gen_tv_nfo_file(mediainfo=mediainfo)
        if doc:
            return doc.toprettyxml(indent="  ", encoding="utf-8")  # noqa

        return None

    def get_metadata_img(
        self,
        mediainfo: MediaInfo,
        season: Optional[int] = None,
        episode: Optional[int] = None,
    ) -> dict:
        """
        获取图片名称和url
        :param mediainfo: 媒体信息
        :param season: 季号
        :param episode: 集号
        """
        images = {}
        if season is not None:
            # 只需要季集的图片
            if episode:
                # 集的图片
                if mediainfo.episode_group:
                    seasoninfo = self.original_tmdb(mediainfo).get_tv_group_detail(
                        mediainfo.episode_group, season
                    )
                else:
                    seasoninfo = self.original_tmdb(mediainfo).get_tv_season_detail(
                        mediainfo.tmdb_id, season
                    )
                if seasoninfo:
                    episodeinfo = self.__get_episode_detail(seasoninfo, episode)
                    if still_path := episodeinfo.get("still_path"):
                        # TMDB集still图片
                        ext = Path(still_path).suffix
                        still_name = f"episode-thumb{ext}"
                        still_url = settings.TMDB_IMAGE_URL(still_path)
                        images[still_name] = still_url
            else:
                # 季的图片
                seasoninfo = self.original_tmdb(mediainfo).get_tv_season_detail(
                    mediainfo.tmdb_id, season
                )
                if seasoninfo:
                    # TMDB季poster图片
                    poster_name, poster_url = self.get_season_poster(seasoninfo, season)
                    if poster_name and poster_url:
                        images[poster_name] = poster_url
        else:
            self.__ensure_main_images(mediainfo)
            # 获取媒体信息中原有图片
            for attr_name, attr_value in vars(mediainfo).items():
                if (
                    attr_value
                    and attr_name.endswith("_path")
                    and isinstance(attr_value, str)
                    and attr_value.startswith("http")
                ):
                    image_name = (
                        attr_name.replace("_path", "") + Path(attr_value).suffix
                    )
                    images[image_name] = attr_value

            # 替换原语言Poster
            if settings.TMDB_SCRAP_ORIGINAL_IMAGE:
                _mediainfo = self.original_tmdb(mediainfo).get_info(
                    mediainfo.type, mediainfo.tmdb_id
                )
                if _mediainfo:
                    for attr_name, attr_value in _mediainfo.items():
                        if attr_name.endswith("_path") and attr_value is not None:
                            image_url = settings.TMDB_IMAGE_URL(attr_value)
                            image_name = (
                                attr_name.replace("_path", "") + Path(image_url).suffix
                            )
                            images[image_name] = image_url
        return images

    def __ensure_main_images(self, mediainfo: MediaInfo) -> None:
        """
        主媒体图片缺失时从 TMDB images 接口回填，避免当前语言没有图时只生成 NFO。
        """
        if not mediainfo or not mediainfo.tmdb_id:
            return
        if mediainfo.poster_path and mediainfo.backdrop_path:
            return
        if mediainfo.type == MediaType.MOVIE:
            image_info = self.default_tmdb.get_movie_images(
                mediainfo.tmdb_id,
                original_language=mediainfo.original_language,
            )
        else:
            image_info = self.default_tmdb.get_tv_images(
                mediainfo.tmdb_id,
                original_language=mediainfo.original_language,
            )
        if not image_info:
            return
        if not mediainfo.poster_path:
            poster_path = self.__pick_best_image_path(image_info.get("posters"))
            if poster_path:
                mediainfo.poster_path = settings.TMDB_IMAGE_URL(poster_path)
        if not mediainfo.backdrop_path:
            backdrop_path = self.__pick_best_image_path(image_info.get("backdrops"))
            if backdrop_path:
                mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(backdrop_path)

    @staticmethod
    def __pick_best_image_path(images: list) -> Optional[str]:
        """
        从 TMDB 图片列表中选择评分和投票数最高的一张。
        """
        if not images:
            return None
        images = sorted(
            images,
            key=lambda item: (
                item.get("vote_average") or 0,
                item.get("vote_count") or 0,
            ),
            reverse=True,
        )
        return images[0].get("file_path")

    @staticmethod
    def get_season_poster(seasoninfo: dict, season: int) -> Tuple[str, str]:
        """
        获取季的海报
        """
        # TMDB季poster图片
        sea_seq = str(season).rjust(2, "0")
        if poster_path := seasoninfo.get("poster_path"):
            # 后缀
            ext = Path(poster_path).suffix
            # URL
            url = settings.TMDB_IMAGE_URL(poster_path)
            # S0海报格式不同
            if season == 0:
                image_name = f"season-specials-poster{ext}"
            else:
                image_name = f"season{sea_seq}-poster{ext}"
            return image_name, url
        return "", ""

    @staticmethod
    def __get_episode_detail(seasoninfo: dict, episode: int) -> dict:
        """
        根据季信息获取集的信息
        """
        for _episode_info in seasoninfo.get("episodes") or []:
            if _episode_info.get("episode_number") == episode:
                return _episode_info
        return {}

    @staticmethod
    def __gen_common_nfo(
        mediainfo: MediaInfo, doc: minidom.Document, root: minidom.Element
    ):
        """
        生成公共NFO
        """
        # TMDB
        DomUtils.add_node(doc, root, "tmdbid", mediainfo.tmdb_id or "")
        uniqueid_tmdb = DomUtils.add_node(
            doc, root, "uniqueid", mediainfo.tmdb_id or ""
        )
        uniqueid_tmdb.setAttribute("type", "tmdb")
        uniqueid_tmdb.setAttribute("default", "true")
        # TVDB
        if mediainfo.tvdb_id:
            DomUtils.add_node(doc, root, "tvdbid", str(mediainfo.tvdb_id))
            uniqueid_tvdb = DomUtils.add_node(
                doc, root, "uniqueid", str(mediainfo.tvdb_id)
            )
            uniqueid_tvdb.setAttribute("type", "tvdb")
        # IMDB
        if mediainfo.imdb_id:
            DomUtils.add_node(doc, root, "imdbid", mediainfo.imdb_id)
            uniqueid_imdb = DomUtils.add_node(doc, root, "uniqueid", mediainfo.imdb_id)
            uniqueid_imdb.setAttribute("type", "imdb")
            uniqueid_imdb.setAttribute("default", "true")
            uniqueid_tmdb.setAttribute("default", "false")

        # 简介
        xplot = DomUtils.add_node(doc, root, "plot")
        xplot.appendChild(doc.createCDATASection(mediainfo.overview or ""))
        xoutline = DomUtils.add_node(doc, root, "outline")
        xoutline.appendChild(doc.createCDATASection(mediainfo.overview or ""))
        # 导演
        for director in mediainfo.directors:
            xdirector = DomUtils.add_node(
                doc, root, "director", director.get("name") or ""
            )
            xdirector.setAttribute("tmdbid", str(director.get("id") or ""))
        # 演员
        for actor in mediainfo.actors:
            # 获取中文名
            xactor = DomUtils.add_node(doc, root, "actor")
            DomUtils.add_node(doc, xactor, "name", actor.get("name") or "")
            DomUtils.add_node(doc, xactor, "type", "Actor")
            DomUtils.add_node(
                doc, xactor, "role", actor.get("character") or actor.get("role") or ""
            )
            DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "")
            if profile_path := actor.get("profile_path"):
                DomUtils.add_node(
                    doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path)
                )
            DomUtils.add_node(
                doc,
                xactor,
                "profile",
                f"https://www.themoviedb.org/person/{actor.get('id')}",
            )
        # 风格
        genres = mediainfo.genres or []
        for genre in genres:
            DomUtils.add_node(doc, root, "genre", genre.get("name") or "")
        # 评分
        DomUtils.add_node(doc, root, "rating", mediainfo.vote_average or "0")
        # 内容分级
        if content_rating := mediainfo.content_rating:
            DomUtils.add_node(doc, root, "mpaa", content_rating)
        # 制作公司
        for company in mediainfo.production_companies or []:
            if company.get("name"):
                DomUtils.add_node(doc, root, "studio", company.get("name"))
        # 制作国家
        for country in mediainfo.production_countries or []:
            if country.get("name"):
                DomUtils.add_node(doc, root, "country", country.get("name"))
        # 时长
        if mediainfo.runtime:
            DomUtils.add_node(doc, root, "runtime", str(mediainfo.runtime))

        return doc

    def __gen_movie_nfo_file(self, mediainfo: MediaInfo) -> minidom.Document:
        """
        生成电影的NFO描述文件
        :param mediainfo: 识别后的媒体信息
        """
        # 开始生成XML
        doc = minidom.Document()
        root = DomUtils.add_node(doc, doc, "movie")
        # 公共部分
        doc = self.__gen_common_nfo(mediainfo=mediainfo, doc=doc, root=root)
        # 标题
        DomUtils.add_node(doc, root, "title", mediainfo.title or "")
        DomUtils.add_node(doc, root, "originaltitle", mediainfo.original_title or "")
        # 发布日期
        DomUtils.add_node(doc, root, "premiered", mediainfo.release_date or "")
        # 年份
        DomUtils.add_node(doc, root, "year", mediainfo.year or "")
        return doc

    def __gen_tv_nfo_file(self, mediainfo: MediaInfo) -> minidom.Document:
        """
        生成电视剧的NFO描述文件
        :param mediainfo: 媒体信息
        """
        # 开始生成XML
        doc = minidom.Document()
        root = DomUtils.add_node(doc, doc, "tvshow")
        # 公共部分
        doc = self.__gen_common_nfo(mediainfo=mediainfo, doc=doc, root=root)
        # 标题
        DomUtils.add_node(doc, root, "title", mediainfo.title or "")
        DomUtils.add_node(doc, root, "originaltitle", mediainfo.original_title or "")
        # 发布日期
        DomUtils.add_node(doc, root, "premiered", mediainfo.release_date or "")
        # 年份
        DomUtils.add_node(doc, root, "year", mediainfo.year or "")
        DomUtils.add_node(doc, root, "season", "-1")
        DomUtils.add_node(doc, root, "episode", "-1")

        return doc

    @staticmethod
    def __gen_tv_season_nfo_file(seasoninfo: dict, season: int) -> minidom.Document:
        """
        生成电视剧季的NFO描述文件
        :param seasoninfo: TMDB季媒体信息
        :param season: 季号
        """
        doc = minidom.Document()
        root = DomUtils.add_node(doc, doc, "season")
        # 简介
        xplot = DomUtils.add_node(doc, root, "plot")
        xplot.appendChild(doc.createCDATASection(seasoninfo.get("overview") or ""))
        xoutline = DomUtils.add_node(doc, root, "outline")
        xoutline.appendChild(doc.createCDATASection(seasoninfo.get("overview") or ""))
        # 标题
        DomUtils.add_node(
            doc, root, "title", seasoninfo.get("name") or "季 %s" % season
        )
        # 发行日期
        DomUtils.add_node(doc, root, "premiered", seasoninfo.get("air_date") or "")
        DomUtils.add_node(doc, root, "releasedate", seasoninfo.get("air_date") or "")
        # 发行年份
        DomUtils.add_node(
            doc,
            root,
            "year",
            seasoninfo.get("air_date")[:4] if seasoninfo.get("air_date") else "",
        )
        # seasonnumber
        DomUtils.add_node(doc, root, "seasonnumber", str(season))
        return doc

    @staticmethod
    def __gen_tv_episode_nfo_file(
        tmdbid: int, episodeinfo: dict, season: int, episode: int
    ) -> minidom.Document:
        """
        生成电视剧集的NFO描述文件
        :param tmdbid: TMDBID
        :param episodeinfo: 集TMDB元数据
        :param season: 季号
        :param episode: 集号
        """
        # 开始生成集的信息
        doc = minidom.Document()
        root = DomUtils.add_node(doc, doc, "episodedetails")
        # TMDBID
        uniqueid = DomUtils.add_node(doc, root, "uniqueid", str(episodeinfo.get("id")))
        uniqueid.setAttribute("type", "tmdb")
        uniqueid.setAttribute("default", "true")
        # tmdbid
        # 应与uniqueid一致 使用剧集id 否则jellyfin/emby会将此id覆盖上面的uniqueid
        DomUtils.add_node(doc, root, "tmdbid", str(episodeinfo.get("id")))
        # 标题
        DomUtils.add_node(
            doc, root, "title", episodeinfo.get("name") or "第 %s 集" % episode
        )
        # 简介
        xplot = DomUtils.add_node(doc, root, "plot")
        xplot.appendChild(doc.createCDATASection(episodeinfo.get("overview") or ""))
        xoutline = DomUtils.add_node(doc, root, "outline")
        xoutline.appendChild(doc.createCDATASection(episodeinfo.get("overview") or ""))
        # 发布日期
        DomUtils.add_node(doc, root, "aired", episodeinfo.get("air_date") or "")
        # 年份
        DomUtils.add_node(
            doc,
            root,
            "year",
            episodeinfo.get("air_date")[:4] if episodeinfo.get("air_date") else "",
        )
        # 季
        DomUtils.add_node(doc, root, "season", str(season))
        # 集
        DomUtils.add_node(doc, root, "episode", str(episode))
        # 评分
        DomUtils.add_node(doc, root, "rating", episodeinfo.get("vote_average") or "0")
        # 导演
        directors = episodeinfo.get("crew") or []
        for director in directors:
            if director.get("known_for_department") == "Directing":
                xdirector = DomUtils.add_node(
                    doc, root, "director", director.get("name") or ""
                )
                xdirector.setAttribute("tmdbid", str(director.get("id") or ""))
        # 演员
        actors = episodeinfo.get("guest_stars") or []
        for actor in actors:
            if actor.get("known_for_department") == "Acting":
                xactor = DomUtils.add_node(doc, root, "actor")
                DomUtils.add_node(doc, xactor, "name", actor.get("name") or "")
                DomUtils.add_node(doc, xactor, "type", "Actor")
                DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "")
                if profile_path := actor.get("profile_path"):
                    DomUtils.add_node(
                        doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path)
                    )
                DomUtils.add_node(
                    doc,
                    xactor,
                    "profile",
                    f"https://www.themoviedb.org/person/{actor.get('id')}",
                )
        return doc
