"""
草稿管理模块

实现微信公众号草稿管理相关API
"""

from typing import Dict, Any, List, Tuple, Optional
from ..core import Auth, Client
from ..utils.image_utils import (
    get_crop_parameters as _get_crop_parameters,
    generate_optimal_crop as _generate_optimal_crop,
    create_cover_info as _create_cover_info,
    create_image_list_item as _create_image_list_item,
)
from ..utils.content_utils import remove_html_comments
from ..utils.content_utils import replace_image_links as _replace_image_links


class Draft:
    """
    草稿管理类

    用于管理微信公众号的草稿
    """

    def __init__(self, auth: Auth):
        """
        初始化草稿管理

        :param auth: 认证管理实例
        """
        self.auth = auth
        self.client = Client(proxy=auth.proxy)

    def draft_switch(self, enable: bool) -> Dict[str, Any]:
        """
        草稿箱开关设置

        :param enable: 是否开启草稿箱，true为开启，false为关闭
        :return: 操作结果
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/switch?access_token={access_token}"

        data = {"enable": enable}
        return self.client.post(url, data)

    def draft_update(
        self, media_id: str, articles: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """
        更新草稿

        :param media_id: 要更新的草稿的media_id
        :param articles: 图文消息内容，数组，包含一个或多个图文
        :return: 操作结果
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/update?access_token={access_token}"

        data = {"media_id": media_id, "articles": articles}
        return self.client.post(url, data)

    def draft_batchget(
        self, offset: int = 0, count: int = 20, no_content: bool = False
    ) -> Dict[str, Any]:
        """
        获取草稿列表

        :param offset: 从全部草稿的该偏移位置开始返回，0表示从第一个草稿返回
        :param count: 返回草稿的数量，取值在1到20之间
        :param no_content: 是否不返回草稿正文，true表示不返回正文，false表示返回正文
        :return: 草稿列表
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token={access_token}"

        data = {"offset": offset, "count": count, "no_content": 1 if no_content else 0}
        return self.client.post(url, data)

    def draft_add(self, articles: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        新增草稿

        :param articles: 图文消息内容，数组，包含一个或多个图文
        :return: 草稿的media_id等信息
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={access_token}"

        data = {"articles": articles}
        return self.client.post(url, data)

    def create_draft(self, articles: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        创建草稿

        :param articles: 图文消息内容，数组，包含一个或多个图文
        :return: 草稿的media_id等信息
        """
        return self.draft_add(articles)

    def get_draft_list(
        self, offset: int = 0, count: int = 20, no_content: bool = False
    ) -> Dict[str, Any]:
        """
        获取草稿列表

        :param offset: 从全部草稿的该偏移位置开始返回，0表示从第一个草稿返回
        :param count: 返回草稿的数量，取值在1到20之间
        :param no_content: 是否不返回草稿正文，true表示不返回正文，false表示返回正文
        :return: 草稿列表
        """
        return self.draft_batchget(offset, count, no_content)

    def update_draft(
        self, media_id: str, articles: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """
        更新草稿

        :param media_id: 要更新的草稿的media_id
        :param articles: 图文消息内容，数组，包含一个或多个图文
        :return: 操作结果
        """
        return self.draft_update(media_id, articles)

    def delete_draft(self, media_id: str) -> Dict[str, Any]:
        """
        删除草稿

        :param media_id: 要删除的草稿的media_id
        :return: 操作结果
        """
        return self.draft_delete(media_id)

    def get_draft_count(self) -> Dict[str, Any]:
        """
        获取草稿总数

        :return: 草稿总数
        """
        return self.draft_count()

    def draft_count(self) -> Dict[str, Any]:
        """
        获取草稿的总数

        :return: 草稿总数
        """
        access_token = self.auth.get_access_token()
        url = (
            f"https://api.weixin.qq.com/cgi-bin/draft/count?access_token={access_token}"
        )

        return self.client.get(url)

    def draft_delete(self, media_id: str) -> Dict[str, Any]:
        """
        删除草稿

        :param media_id: 要删除的草稿的media_id
        :return: 操作结果
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/delete?access_token={access_token}"

        data = {"media_id": media_id}
        return self.client.post(url, data)

    def get_draft(self, media_id: str) -> Dict[str, Any]:
        """
        获取草稿详情

        :param media_id: 要获取的草稿的media_id
        :return: 草稿详情
        """
        access_token = self.auth.get_access_token()
        url = f"https://api.weixin.qq.com/cgi-bin/draft/get?access_token={access_token}"

        data = {"media_id": media_id}
        return self.client.post(url, data)

    def create_article(
        self, is_news: bool = True, auto_draft: bool = False, **kwargs
    ) -> Dict[str, Any]:
        """
        创建文章对象，参数参考文档：
        https://developers.weixin.qq.com/doc/service/api/draftbox/draftmanage/api_draft_add.html#Body-articles-Array-Object-Payload

        :param is_news: 是否为图文消息，True为图文消息（news），False为图片消息（newspic）
        :param auto_draft: 是否自动创建草稿，True为自动创建，False为只返回文章对象
        :param kwargs: 文章属性
        :return: 文章对象或草稿创建结果
        """
        article_type = "news" if is_news else "newspic"
        article = {
            "article_type": article_type,
        }

        # 只添加非None的参数
        if kwargs.get("title") is not None:
            article["title"] = kwargs["title"]
        if kwargs.get("author") is not None:
            article["author"] = kwargs["author"]
        if kwargs.get("digest") is not None:
            article["digest"] = kwargs["digest"]
        if kwargs.get("content") is not None:
            article["content"] = kwargs["content"]
        if kwargs.get("content_source_url") is not None:
            article["content_source_url"] = kwargs["content_source_url"]
        if kwargs.get("need_open_comment") is not None:
            article["need_open_comment"] = 1 if kwargs["need_open_comment"] else 0
        if kwargs.get("only_fans_can_comment") is not None:
            article["only_fans_can_comment"] = (
                1 if kwargs["only_fans_can_comment"] else 0
            )

        # 图文消息需要封面图片
        if is_news:
            if kwargs.get("thumb_media_id") is not None:
                article["thumb_media_id"] = kwargs["thumb_media_id"]
        # 图片消息需要图片信息
        else:
            if kwargs.get("image_info") is not None:
                article["image_info"] = kwargs["image_info"]
            if kwargs.get("cover_info") is not None:
                article["cover_info"] = kwargs["cover_info"]

        # 自动创建草稿
        if auto_draft:
            return self.draft_add(articles=[article])

        return article

    def add_news_article(
        self,
        title: str,
        content: str = None,
        content_file: Optional[str] = None,
        thumb_media_id: Optional[str] = None,
        thumb_image_path: str = None,
        thumb_image_content: bytes = None,
        thumb_image_url: str = None,
        author: str = None,
        digest: str = None,
        content_source_url: str = None,
        need_open_comment: bool = False,
        only_fans_can_comment: bool = False,
        auto_draft: bool = True,
        content_processor: Any = False,
        validate: bool = False,
        replace_image_links: bool = False,
        skip_domains: Tuple[str, ...] = ("qpic.cn",),
        custom_image_pattern: Any = None,
    ) -> Dict[str, Any]:
        """
        添加图文消息文章

        :param title: 标题（必填，不超过32字）
        :param content: 内容（必填，不超过2万字符，小于1M），支持直接传入文件路径或文件内容
        :param content_file: 内容文件路径（与 content 二选一，优先使用 content_file）
        :param thumb_media_id: 封面图片素材id（优先使用）
        :param thumb_image_path: 封面图片文件路径
        :param thumb_image_content: 封面图片二进制内容
        :param thumb_image_url: 封面图片URL
        :param author: 作者（不超过16字）
        :param digest: 摘要（不超过128字）
        :param content_source_url: 原文链接（不超过1kb）
        :param need_open_comment: 是否打开评论
        :param only_fans_can_comment: 是否粉丝才可评论
        :param auto_draft: 是否自动创建草稿，True为自动创建，False为只返回文章对象
        :param content_processor: 内容处理器，可选值：
                                  - True: 使用默认方案，自动去除HTML注释 <!-- -->
                                  - False: 不启用处理器
                                  - callable: 自定义回调函数，接收文件路径或内容字符串，返回处理后的内容
        :param validate: 是否启用参数校验，True启用，False禁用
        :param replace_image_links: 是否替换图片链接为图文消息图片链接，False不替换（默认）
        :param skip_domains: 要跳过的域名元组，默认跳过 qpic.cn
        :param custom_image_pattern: 自定义图片匹配规则，可选值：
                                     - str: 正则表达式字符串
                                     - tuple: (pattern, flags) 元组
                                     - callable: 回调函数，接收图片URL字符串，返回True表示匹配
        :return: 操作结果或文章对象
        """
        if content_file:
            content = self._read_content_file(content_file)

        if content_processor is True:
            content = remove_html_comments(content)
        elif callable(content_processor):
            content = content_processor(content)

        if replace_image_links:
            print("🔄 开始替换图片链接...")
            content = _replace_image_links(
                content=content,
                upload_func=self.upload_article_image,
                skip_domains=skip_domains,
                custom_pattern=custom_image_pattern,
                max_concurrency=3,
            )

        if content is None:
            raise ValueError("请提供内容或内容文件")

        if validate:
            if len(title) > 32:
                raise ValueError("标题长度不能超过32个字符")
            if len(content) > 20000:
                raise ValueError("内容长度不能超过20000个字符")
            if author is not None and len(author) > 8:
                raise ValueError("作者长度不能超过8个字符")
            if digest is not None and len(digest) > 128:
                raise ValueError("摘要长度不能超过128个字符")
            if content_source_url is not None and len(content_source_url) > 1024:
                raise ValueError("原文链接长度不能超过1024个字符")

        # 处理封面图片
        if not thumb_media_id:
            from .asset import Asset

            asset = Asset(auth=self.auth)

            if thumb_image_path:
                # 从文件路径上传图片
                result = asset.add_material(
                    media_type="image", media_path=thumb_image_path
                )
                thumb_media_id = result.get("media_id")
            elif thumb_image_content:
                # 从二进制内容上传图片
                result = asset.add_material(
                    media_type="image", media_content=thumb_image_content
                )
                thumb_media_id = result.get("media_id")
            elif thumb_image_url:
                # 从URL下载图片并上传
                import requests

                try:
                    response = requests.get(thumb_image_url)
                    response.raise_for_status()
                    image_content = response.content
                    result = asset.add_material(
                        media_type="image", media_content=image_content
                    )
                    thumb_media_id = result.get("media_id")
                except Exception as e:
                    raise ValueError(f"从URL下载图片失败: {str(e)}")
            else:
                raise ValueError("请提供封面图片的media_id、文件路径、二进制内容或URL")

        # 确保使用永久MediaID
        # 这里简化处理，直接使用上传的media_id，实际应用中可能需要判断是否为永久MediaID

        return self.create_article(
            is_news=True,
            auto_draft=auto_draft,
            title=title,
            content=content,
            thumb_media_id=thumb_media_id,
            author=author,
            digest=digest,
            content_source_url=content_source_url,
            need_open_comment=need_open_comment,
            only_fans_can_comment=only_fans_can_comment,
        )

    def add_newspic_article(
        self,
        title: str,
        image_list: list,
        author: str = None,
        digest: str = None,
        content: str = None,
        content_source_url: str = None,
        need_open_comment: bool = False,
        only_fans_can_comment: bool = False,
        cover_info: dict = None,
        auto_draft: bool = True,
    ) -> Dict[str, Any]:
        """
        添加图片消息文章

        :param title: 标题（必填，不超过32字）
        :param image_list: 图片列表（必填，最多20张），支持以下格式：
                          1. 字符串：本地图片路径或网络图片URL
                          2. 字典：{"image_media_id": "media_id"}（推荐）
                          3. 字典：{"path": "本地图片路径"}
                          4. 字典：{"url": "网络图片URL"}
        :param author: 作者（不超过16字）
        :param digest: 摘要（不超过128字）
        :param content: 内容（不超过2万字符，小于1M）
        :param content_source_url: 原文链接（不超过1kb）
        :param need_open_comment: 是否打开评论
        :param only_fans_can_comment: 是否粉丝才可评论
        :param cover_info: 封面信息
        :param auto_draft: 是否自动创建草稿，True为自动创建，False为只返回文章对象
        :return: 操作结果或文章对象
        """
        # 参数校验
        if len(title) > 32:
            raise ValueError("标题长度不能超过32个字符")
        if not image_list or len(image_list) > 20:
            raise ValueError("图片列表不能为空且不能超过20张")
        if author is not None and len(author) > 16:
            raise ValueError("作者长度不能超过16个字符")
        if digest is not None and len(digest) > 128:
            raise ValueError("摘要长度不能超过128个字符")
        if content is not None and len(content) > 20000:
            raise ValueError("内容长度不能超过20000个字符")
        if content_source_url is not None and len(content_source_url) > 1024:
            raise ValueError("原文链接长度不能超过1024个字符")

        # 处理图片列表，支持本地图片和网络图片
        processed_image_list = []
        from .asset import Asset

        asset = Asset(auth=self.auth)

        for item in image_list:
            image_media_id = None

            if isinstance(item, str):
                # 字符串类型，可能是本地图片路径或网络图片URL
                if item.startswith(("http://", "https://")):
                    # 网络图片URL
                    try:
                        import requests

                        response = requests.get(item)
                        response.raise_for_status()
                        image_content = response.content
                        result = asset.add_material(
                            media_type="image", media_content=image_content
                        )
                        image_media_id = result.get("media_id")
                    except Exception as e:
                        raise ValueError(f"从URL下载图片失败: {str(e)}")
                else:
                    # 本地图片路径
                    try:
                        result = asset.add_material(media_type="image", media_path=item)
                        image_media_id = result.get("media_id")
                    except Exception as e:
                        raise ValueError(f"上传本地图片失败: {str(e)}")
            elif isinstance(item, dict):
                # 字典类型
                if "image_media_id" in item:
                    # 直接提供了media_id
                    image_media_id = item["image_media_id"]
                elif "path" in item:
                    # 本地图片路径
                    try:
                        result = asset.add_material(
                            media_type="image", media_path=item["path"]
                        )
                        image_media_id = result.get("media_id")
                    except Exception as e:
                        raise ValueError(f"上传本地图片失败: {str(e)}")
                elif "url" in item:
                    # 网络图片URL
                    try:
                        import requests

                        response = requests.get(item["url"])
                        response.raise_for_status()
                        image_content = response.content
                        result = asset.add_material(
                            media_type="image", media_content=image_content
                        )
                        image_media_id = result.get("media_id")
                    except Exception as e:
                        raise ValueError(f"从URL下载图片失败: {str(e)}")
                else:
                    raise ValueError(f"图片列表项格式不正确: {item}")
            else:
                raise ValueError(f"图片列表项类型不正确: {type(item)}")

            if not image_media_id:
                raise ValueError(f"获取图片media_id失败: {item}")

            processed_image_list.append({"image_media_id": image_media_id})

        return self.create_article(
            is_news=False,
            auto_draft=auto_draft,
            title=title,
            image_info={"image_list": processed_image_list},
            author=author,
            digest=digest,
            content=content,
            content_source_url=content_source_url,
            need_open_comment=need_open_comment,
            only_fans_can_comment=only_fans_can_comment,
            cover_info=cover_info,
        )

    def create_multi_article_draft_from_dir(
        self,
        dir_path: str,
        skip_domains: tuple = ("qpic.cn",),
        content_processor: Any = True,
        validate: bool = False,
        replace_image_links: bool = True,
        verbose: bool = True,
        sort_key: Any = True,
    ) -> Dict[str, Any]:
        """
        从目录创建多图文草稿

        读取目录下所有HTML文件，从文件头提取元数据，创建包含多篇文章的草稿

        :param dir_path: 目录路径，目录下应包含多个HTML文件，每个文件包含文章元数据和内容
        :param skip_domains: 要跳过的域名元组
        :param content_processor: 内容处理器
                               - True: 使用默认方案，自动去除HTML注释
                               - False: 不启用处理器
                               - callable: 自定义回调函数
        :param validate: 是否启用参数校验
        :param replace_image_links: 是否替换图片链接为图文消息图片链接
        :param verbose: 是否打印详细信息
        :param sort_key: 文章排序规则，支持以下类型：
                         - False: 不启用排序，使用 os.listdir 返回的原始顺序
                         - True: 启用默认排序规则，按文件名开头的数字排序（如 1-xxx.html, 2-xxx.html）
                         - callable: 自定义排序函数，接收文件路径字符串，返回用于排序的值
                           示例: sort_key=lambda fp: int(os.path.basename(fp).split('-')[0])
        :return: 包含执行结果的字典
                 - success: 是否成功
                 - media_id: 草稿media_id（成功时）
                 - articles_count: 文章数量
                 - articles: 每篇文章的详细信息列表
                 - error: 错误信息（失败时）
        """
        import os
        import re

        from ..utils.content_utils import (
            parse_article_metadata,
            extract_content_from_html,
            replace_image_links as replace_links,
        )

        result = {
            "success": False,
            "media_id": None,
            "articles_count": 0,
            "articles": [],
            "error": None,
        }

        if verbose:
            print(f"\n{'=' * 60}")
            print("📚 开始创建多图文草稿")
            print(f"📁 目录: {dir_path}")
            print(f"{'=' * 60}")

        if not os.path.isdir(dir_path):
            result["error"] = f"目录不存在: {dir_path}"
            if verbose:
                print(f"❌ {result['error']}")
            return result

        file_paths = [
            os.path.join(dir_path, f)
            for f in os.listdir(dir_path)
            if f.endswith((".html", ".htm"))
        ]

        def _default_sort_key(file_path: str) -> Any:
            filename = os.path.basename(file_path)
            match = re.match(r"^(\d+)", filename)
            if match:
                return (0, int(match.group(1)))
            return (1, filename)

        if callable(sort_key):
            try:
                file_paths = sorted(file_paths, key=sort_key)
                if verbose:
                    print("📋 使用自定义排序规则对文件进行排序")
            except Exception as e:
                if verbose:
                    print(f"⚠️ 自定义排序规则执行失败，使用默认规则: {e}")
                file_paths = sorted(file_paths, key=_default_sort_key)
                if verbose:
                    print("📋 回退到默认规则（文件名开头数字）对文件进行排序")
        else:
            file_paths = sorted(file_paths, key=_default_sort_key)
            if verbose:
                print("📋 使用默认规则（文件名开头数字）对文件进行排序")

        if not file_paths:
            result["error"] = f"目录中没有找到HTML文件: {dir_path}"
            if verbose:
                print(f"❌ {result['error']}")
            return result

        if verbose:
            print(f"📄 找到 {len(file_paths)} 个HTML文件")

        articles_metadata = []
        parsing_errors = []

        for fp in file_paths:
            try:
                metadata = parse_article_metadata(fp)
                content = extract_content_from_html(fp)
                metadata["_file_path"] = fp
                metadata["_content"] = content
                articles_metadata.append(metadata)
                if verbose:
                    print(
                        f"  ✅ [{len(articles_metadata)}] {metadata.get('title', '无标题')}"
                    )
            except Exception as e:
                parsing_errors.append({"file": fp, "error": str(e)})
                if verbose:
                    print(f"  ❌ {os.path.basename(fp)}: {e}")

        if not articles_metadata:
            result["error"] = "没有成功解析任何文章，所有文件解析失败"
            if verbose:
                print(f"❌ {result['error']}")
            return result

        if verbose:
            print("\n🔑 认证已创建")

        from .asset import Asset

        asset = Asset(auth=self.auth)

        if verbose:
            print("✅ 初始化完成")

        processed_articles = []
        article_processing_errors = []

        for idx, meta in enumerate(articles_metadata, 1):
            try:
                if verbose:
                    print(f"\n📝 处理第 {idx} 篇文章: {meta.get('title', '无标题')}")

                thumb_media_id = meta.get("thumb_media_id")
                cover_local_path = meta.get("cover_local_path")
                cover_url = meta.get("cover_url")

                if not thumb_media_id:
                    if cover_local_path and os.path.exists(cover_local_path):
                        if verbose:
                            print(f"  📤 上传本地封面: {cover_local_path}")
                        asset_result = asset.add_material(
                            media_type="image", media_path=cover_local_path
                        )
                        thumb_media_id = asset_result.get("media_id")
                        if verbose:
                            print(f"  ✅ 封面上传成功，media_id: {thumb_media_id}")
                    elif cover_url:
                        if verbose:
                            print(f"  📤 下载并上传封面: {cover_url}")
                        import requests

                        response = requests.get(cover_url)
                        response.raise_for_status()
                        asset_result = asset.add_material(
                            media_type="image", media_content=response.content
                        )
                        thumb_media_id = asset_result.get("media_id")
                        if verbose:
                            print(f"  ✅ 封面上传成功，media_id: {thumb_media_id}")
                    else:
                        raise ValueError(
                            "缺少封面图片信息（thumb_media_id、cover_local_path或cover_url）"
                        )

                content = meta.get("_content", "")

                if replace_image_links:
                    if verbose:
                        print("  🔄 替换图片链接...")
                    content = replace_links(
                        content=content,
                        auth=self.auth,
                        skip_domains=skip_domains,
                        max_concurrency=3,
                    )
                    if verbose:
                        print("  ✅ 图片链接替换完成")

                if content_processor is True:
                    content = re.sub(r"<!--[\s\S]*?-->", "", content)
                elif callable(content_processor):
                    content = content_processor(content)

                if validate:
                    if len(meta.get("title", "")) > 32:
                        raise ValueError(f"标题长度超过32字符: {meta.get('title')}")
                    if len(content) > 20000:
                        raise ValueError(f"内容长度超过20000字符: {len(content)}")

                article = self.create_article(
                    is_news=True,
                    auto_draft=False,
                    title=meta.get("title"),
                    content=content,
                    thumb_media_id=thumb_media_id,
                    author=meta.get("author"),
                    digest=meta.get("digest"),
                    need_open_comment=True,
                    only_fans_can_comment=True,
                )
                processed_articles.append(article)
                result["articles"].append(
                    {
                        "title": meta.get("title"),
                        "thumb_media_id": thumb_media_id,
                        "file_path": meta.get("_file_path"),
                    }
                )
                if verbose:
                    print("  ✅ 文章处理完成")

            except Exception as e:
                error_msg = f"处理文章 '{meta.get('title', '未知')}' 失败: {type(e).__name__}: {e}"
                article_processing_errors.append(
                    {"article": meta.get("title"), "error": str(e)}
                )
                if verbose:
                    print(f"  ❌ {error_msg}")

        if not processed_articles:
            result["error"] = "没有成功处理任何文章，所有文章处理失败"
            if verbose:
                print(f"❌ {result['error']}")
            return result

        if verbose:
            print("\n📤 创建多图文草稿...")

        try:
            draft_result = self.draft_add(articles=processed_articles)
            if "media_id" in draft_result:
                result["success"] = True
                result["media_id"] = draft_result["media_id"]
                result["articles_count"] = len(processed_articles)
                if verbose:
                    print("🎉 多图文草稿创建成功！")
                    print(f"   media_id: {result['media_id']}")
                    print(f"   文章数量: {result['articles_count']}")
            else:
                result["error"] = f"草稿创建返回异常: {draft_result}"
                if verbose:
                    print(f"❌ {result['error']}")
        except Exception as e:
            result["error"] = f"创建草稿失败: {type(e).__name__}: {e}"
            if verbose:
                print(f"❌ {result['error']}")

        if parsing_errors and verbose:
            print("\n⚠️ 文件解析警告:")
            for err in parsing_errors:
                print(f"   {os.path.basename(err['file'])}: {err['error']}")

        if article_processing_errors and verbose:
            print("\n⚠️ 文章处理警告:")
            for err in article_processing_errors:
                print(f"   {err['article']}: {err['error']}")

        return result

    def upload_article_image(
        self, image_path: str = None, image_content: bytes = None, image_url: str = None
    ) -> Dict[str, Any]:
        """
        上传图文消息内的图片获取URL

        :param image_path: 图片文件路径
        :param image_content: 图片二进制内容
        :param image_url: 网络图片URL
        :return: 图片URL
        """
        from .asset import Asset

        asset = Asset(auth=self.auth)
        return asset.upload_image(
            image_path=image_path, image_content=image_content, image_url=image_url
        )

    def upload_permanent_image(
        self, image_path: str = None, image_content: bytes = None
    ) -> Dict[str, Any]:
        """
        上传图片作为永久素材获取media_id

        :param image_path: 图片文件路径
        :param image_content: 图片二进制内容
        :return: 图片media_id
        """
        # 导入 Asset 类
        from .asset import Asset

        asset = Asset(auth=self.auth)
        return asset.add_material(
            media_type="image", media_path=image_path, media_content=image_content
        )

    def create_image_list_item(self, image_media_id: str) -> Dict[str, Any]:
        """
        创建图片列表项

        :param image_media_id: 图片素材id
        :return: 图片列表项
        """
        return _create_image_list_item(image_media_id)

    def create_cover_info(self, crop_percent_list: list = None) -> Dict[str, Any]:
        """
        创建封面信息

        :param crop_percent_list: 封面裁剪信息
        :return: 封面信息
        """
        return _create_cover_info(crop_percent_list)

    def get_crop_parameters(
        self,
        image_source: Any,
        start_point: Tuple[int, int],
        crop_width_px: int,
        auto_adjust: bool = True,
        supported_ratios: List[str] = None,
        return_format: str = "all",
    ) -> Optional[Dict[str, Any]]:
        """
        生成微信公众号草稿接口所需的图片裁剪参数
        支持多种图片输入方式和裁剪比例，提供灵活的裁剪参数生成

        :param image_source: 图片来源，支持以下格式：
                            1. 字符串：本地图片文件路径或网络图片URL
                            2. bytes：图片二进制内容
                            3. PIL.Image对象：已打开的图片对象
        :param start_point: 裁剪起点坐标 (x, y)，相对于图片左上角
        :param crop_width_px: 裁剪宽度（像素）
        :param auto_adjust: 当裁剪尺寸超出图片边界时是否自动调整，默认True
        :param supported_ratios: 支持的裁剪比例列表，默认["2.35_1", "1_1", "16_9"]
        :param return_format: 返回格式，可选值：
                            "all": 返回所有格式的裁剪参数
                            "pic_crop": 仅返回pic_crop_*格式参数
                            "crop_percent": 仅返回crop_percent_list格式参数
        :return: 裁剪参数字典，包含以下字段：
                - pic_crop_235_1: 2.35:1比例裁剪参数
                - pic_crop_1_1: 1:1比例裁剪参数
                - pic_crop_16_9: 16:9比例裁剪参数
                - crop_percent_list: 通用裁剪参数列表
        """
        return _get_crop_parameters(
            image_source=image_source,
            start_point=start_point,
            crop_width_px=crop_width_px,
            auto_adjust=auto_adjust,
            supported_ratios=supported_ratios,
            return_format=return_format,
        )

    def generate_optimal_crop(
        self, image_source: Any, target_ratio: str, focus_point: Tuple[int, int] = None
    ) -> Dict[str, Any]:
        """
        为指定比例生成最优裁剪参数，基于图片中心或指定焦点

        :param image_source: 图片来源，同get_crop_parameters
        :param target_ratio: 目标裁剪比例，如"2.35_1", "1_1", "16_9"
        :param focus_point: 焦点坐标 (x, y)，默认使用图片中心
        :return: 裁剪参数字典
        """
        return _generate_optimal_crop(
            image_source=image_source,
            target_ratio=target_ratio,
            focus_point=focus_point,
        )

    def _read_content_file(self, content_file: Any) -> str:
        """
        读取内容文件

        :param content_file: 文件路径或文件内容
        :return: 文件内容字符串
        """
        if isinstance(content_file, (str, bytes)):
            if isinstance(content_file, str):
                if content_file.startswith(("http://", "https://")):
                    import requests

                    response = requests.get(content_file)
                    response.raise_for_status()
                    return response.text
                else:
                    with open(content_file, "r", encoding="utf-8") as f:
                        return f.read()
            else:
                return content_file.decode("utf-8")
        else:
            raise ValueError(f"不支持的文件类型: {type(content_file)}")
