import shutil
from pathlib import Path
from typing import Union

import ruamel.yaml
from ruamel.yaml import CommentedMap

from app.core.config import settings
from app.log import logger
from app.schemas.category import CategoryConfig
from app.utils.singleton import WeakSingleton

HEADER_COMMENTS = """####### 配置说明 #######
# 1. 该配置文件用于配置电影和电视剧的分类策略，配置后程序会按照配置的分类策略名称进行分类，配置文件采用yaml格式，需要严格符合语法规则
# 2. 配置文件中的一级分类名称：`movie`、`tv` 为固定名称不可修改，二级名称同时也是目录名称，会按先后顺序匹配，匹配后程序会按这个名称建立二级目录
# 3. 支持的分类条件：
#   `original_language` 语种，具体含义参考下方字典
#   `production_countries` 国家或地区（电影）、`origin_country` 国家或地区（电视剧），具体含义参考下方字典
#   `genre_ids` 内容类型，具体含义参考下方字典
#   `release_year` 发行年份，格式：YYYY，电影实际对应`release_date`字段，电视剧实际对应`first_air_date`字段，支持范围设定，如：`YYYY-YYYY`
#   themoviedb 详情API返回的其它一级字段
# 4. 配置多项条件时需要同时满足，一个条件需要匹配多个值是使用`,`分隔
# 5. !条件值表示排除该值

"""


class CategoryHelper(metaclass=WeakSingleton):
    """
    二级分类
    """

    def __init__(self):
        self._category_path: Path = settings.CONFIG_PATH / "category.yaml"
        self._categorys = {}
        self._movie_categorys = {}
        self._tv_categorys = {}
        self.init()

    def init(self):
        """
        初始化
        """
        try:
            if not self._category_path.exists():
                shutil.copy(settings.INNER_CONFIG_PATH / "category.yaml", self._category_path)
            with open(self._category_path, mode='r', encoding='utf-8', errors='replace') as f:
                try:
                    yaml_loader = ruamel.yaml.YAML()
                    self._categorys = yaml_loader.load(f)
                except Exception as e:
                    logger.warn(f"二级分类策略配置文件格式出现严重错误！请检查：{str(e)}")
                    self._categorys = {}
        except Exception as err:
            logger.warn(f"二级分类策略配置文件加载出错：{str(err)}")

        if self._categorys:
            self._movie_categorys = self._categorys.get('movie')
            self._tv_categorys = self._categorys.get('tv')
        logger.info(f"已加载二级分类策略 category.yaml")

    def load(self) -> CategoryConfig:
        """
        加载配置
        """
        config = CategoryConfig()
        if not self._category_path.exists():
            return config
        try:
            with open(self._category_path, 'r', encoding='utf-8', errors='replace') as f:
                yaml_loader = ruamel.yaml.YAML()
                data = yaml_loader.load(f)
                if data:
                    config = CategoryConfig(**data)
        except Exception as e:
            logger.error(f"Load category config failed: {e}")
        return config

    def save(self, config: CategoryConfig) -> bool:
        """
        保存配置
        """
        data = config.model_dump(exclude_none=True)
        try:
            with open(self._category_path, 'w', encoding='utf-8') as f:
                f.write(HEADER_COMMENTS)
                yaml_dumper = ruamel.yaml.YAML()
                yaml_dumper.dump(data, f)
            # 保存后重新加载配置
            self.init()
            return True
        except Exception as e:
            logger.error(f"Save category config failed: {e}")
            return False

    @property
    def is_movie_category(self) -> bool:
        """
        获取电影分类标志
        """
        if self._movie_categorys:
            return True
        return False

    @property
    def is_tv_category(self) -> bool:
        """
        获取电视剧分类标志
        """
        if self._tv_categorys:
            return True
        return False

    @property
    def movie_categorys(self) -> list:
        """
        获取电影分类清单
        """
        if not self._movie_categorys:
            return []
        return list(self._movie_categorys.keys())

    @property
    def tv_categorys(self) -> list:
        """
        获取电视剧分类清单
        """
        if not self._tv_categorys:
            return []
        return list(self._tv_categorys.keys())

    def get_movie_category(self, tmdb_info) -> str:
        """
        判断电影的分类
        :param tmdb_info: 识别的TMDB中的信息
        :return: 二级分类的名称
        """
        return self.get_category(self._movie_categorys, tmdb_info)

    def get_tv_category(self, tmdb_info) -> str:
        """
        判断电视剧的分类，包括动漫
        :param tmdb_info: 识别的TMDB中的信息
        :return: 二级分类的名称
        """
        return self.get_category(self._tv_categorys, tmdb_info)

    @staticmethod
    def get_category(categorys: Union[dict, CommentedMap], tmdb_info: dict) -> str:
        """
        根据 TMDB信息与分类配置文件进行比较，确定所属分类
        :param categorys: 分类配置
        :param tmdb_info: TMDB信息
        :return: 分类的名称
        """
        if not tmdb_info:
            return ""
        if not categorys:
            return ""

        for key, item in categorys.items():
            if not item:
                return key
            match_flag = True
            for attr, value in item.items():
                if not value:
                    continue
                if attr == "release_year":
                    # 发行年份
                    info_value = tmdb_info.get("release_date") or tmdb_info.get("first_air_date")
                    if info_value:
                        info_value = str(info_value)[:4]
                else:
                    info_value = tmdb_info.get(attr)
                if not info_value:
                    match_flag = False
                    continue
                elif attr == "production_countries":
                    # 制片国家
                    info_values = [str(val.get("iso_3166_1")).upper() for val in info_value]  # type: ignore
                else:
                    if isinstance(info_value, list):
                        info_values = [str(val).upper() for val in info_value]
                    else:
                        info_values = [str(info_value).upper()]

                values = []
                invert_values = []

                # 如果有 "," 进行分割
                values = [str(val) for val in value.split(",") if val]

                expanded_values = []
                for v in values:
                    if "-" not in v:
                        expanded_values.append(v)
                        continue

                    # - 表示范围
                    value_begin, value_end = v.split("-", 1)

                    prefix = ""
                    if value_begin.startswith('!'):
                        prefix = '!'
                        value_begin = value_begin[1:]

                    if value_begin.isdigit() and value_end.isdigit():
                        # 数字范围
                        expanded_values.extend(f"{prefix}{val}" for val in range(int(value_begin), int(value_end) + 1))
                    else:
                        # 字符串范围
                        expanded_values.extend([f"{prefix}{value_begin}", f"{prefix}{value_end}"])

                values = list(map(str.upper, expanded_values))

                invert_values = [val[1:] for val in values if val.startswith('!')]
                values = [val for val in values if not val.startswith('!')]

                if values and not set(values).intersection(set(info_values)):
                    match_flag = False
                if invert_values and set(invert_values).intersection(set(info_values)):
                    match_flag = False
            if match_flag:
                return key
        return ""
