"""
内容处理工具模块

包含内容处理、图片链接替换等通用功能
"""

import os
import re
from typing import Tuple, Callable, Any
from concurrent.futures import ThreadPoolExecutor, as_completed


def remove_html_comments(content: str) -> str:
    """
    去除HTML注释

    :param content: HTML内容
    :return: 去除注释后的内容
    """
    return re.sub(r"<!--[\s\S]*?-->", "", content)


def extract_image_tags(content: str) -> list:
    """
    提取HTML内容中的所有图片标签

    :param content: HTML内容
    :return: 图片标签列表
    """
    img_pattern = r"<img[^>]+>"
    return re.findall(img_pattern, content)


def extract_image_urls(content: str) -> list:
    """
    提取HTML内容中的所有图片URL

    :param content: HTML内容
    :return: 图片URL列表
    """
    img_pattern = r"<img[^>]+>"
    src_pattern = r'src=["\']([^"\']+)["\']'

    urls = []
    for img_tag in re.findall(img_pattern, content):
        src_match = re.search(src_pattern, img_tag)
        if src_match:
            urls.append(src_match.group(1))
    return urls


def _wechat_upload(image_url: str, auth: Any) -> dict:
    """
    微信图片上传函数

    :param image_url: 图片URL
    :param auth: Auth 认证实例
    :return: 包含 url 字段的字典
    """
    import html
    import requests
    from io import BytesIO

    access_token = auth.get_access_token()
    url = (
        f"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={access_token}"
    )

    image_url = html.unescape(image_url)
    print(f"🔍 准备下载图片: {repr(image_url)}")
    try:
        response = requests.get(image_url.strip(), timeout=10)
        response.raise_for_status()
    except Exception as e:
        print(
            f"⚠️  下载图片失败: {repr(image_url.strip())}, 错误: {type(e).__name__}: {e}"
        )
        raise

    files = {"media": ("image.jpg", BytesIO(response.content), "image/jpeg")}
    result = requests.post(url, files=files, timeout=10)
    result.raise_for_status()
    return result.json()


def replace_image_links(
    content: str = None,
    file_path: str = None,
    auth: Any = None,
    upload_func: Callable[[str], dict] = None,
    skip_domains: Tuple[str, ...] = ("qpic.cn",),
    custom_pattern: Any = None,
    max_concurrency: int = 3,
    remove_comments: bool = False,
    inplace: bool = False,
    output_path: str = None,
    progress_callback: Callable[[int, int, str], None] = None,
    verbose: bool = True,
) -> str:
    """
    替换内容中的图片链接为图文消息图片链接（支持异步并发）

    :param content: HTML内容（与 file_path 二选一）
    :param file_path: HTML文件路径（与 content 二选一）
    :param auth: Auth 认证实例，用于默认的微信上传函数
    :param upload_func: 图片上传函数，接收图片URL字符串，返回包含url字段的字典
                       示例：lambda url: {"url": "https://mmbiz.qpic.cn/..."}
                       如果不提供，将使用 auth 对应的微信上传函数
    :param skip_domains: 要跳过的域名元组，默认跳过 qpic.cn
    :param custom_pattern: 自定义匹配规则：
                          - str: 正则表达式字符串
                          - tuple: (pattern, flags) 元组
                          - callable: 回调函数，接收图片URL字符串，返回True表示匹配
    :param max_concurrency: 最大并发数，默认3
    :param remove_comments: 是否自动去除HTML注释，默认False
    :param inplace: 是否原地替换文件（仅当 file_path 指定时有效），默认False
    :param output_path: 输出文件路径（仅当 file_path 指定时有效），默认为None表示覆盖原文件
    :param progress_callback: 进度回调函数，参数为 (current, total, message)
    :param verbose: 是否打印详细信息，默认True
    :return: 替换后的内容
    """
    if content is None and file_path is None:
        raise ValueError("❌ 必须提供 content 或 file_path 其中之一")
    if content is not None and file_path is not None:
        raise ValueError("❌ content 和 file_path 不能同时提供")

    if upload_func is None:
        if auth is None:
            raise ValueError("❌ 必须提供 upload_func 图片上传函数或 auth 认证实例")

        def upload_func(url: str) -> dict:
            return _wechat_upload(url, auth)

    if file_path:
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"❌ 文件不存在: {file_path}")
        with open(file_path, "r", encoding="utf-8") as f:
            content = f.read()

    if remove_comments:
        content = remove_html_comments(content)

    img_pattern = r"<img[^>]+>"
    src_pattern = r'src=["\']([^"\']+)["\']'

    matches = list(re.finditer(img_pattern, content))
    total = len(matches)

    if total == 0:
        result_content = content
    else:
        src_url_to_new_url = {}

        def get_new_url(url):
            if url in src_url_to_new_url:
                return src_url_to_new_url[url]
            try:
                result = upload_func(url)
                new_url = result.get("url") if isinstance(result, dict) else None
                if new_url:
                    src_url_to_new_url[url] = new_url
                    if verbose:
                        print(f"☑️  图片替换成功: {url} ➡️ {new_url}")
                return new_url
            except Exception as e:
                if verbose:
                    print(f"❌ 图片上传失败: {url}, 错误: {type(e).__name__}: {e}")
                return None

        unique_urls = []
        url_to_matches = {}
        for match in matches:
            img_tag = match.group(0)
            src_match = re.search(src_pattern, img_tag)
            if not src_match:
                continue
            original_url = src_match.group(1)
            url_lower = original_url.lower()
            if any(domain in url_lower for domain in skip_domains):
                continue
            if custom_pattern:
                if isinstance(custom_pattern, (str, tuple)):
                    if isinstance(custom_pattern, tuple):
                        if not re.search(
                            custom_pattern[0],
                            original_url,
                            custom_pattern[1] if len(custom_pattern) > 1 else None,
                        ):
                            continue
                    else:
                        if not re.search(custom_pattern, original_url):
                            continue
                elif callable(custom_pattern):
                    if not custom_pattern(original_url):
                        continue
            if original_url not in url_to_matches:
                url_to_matches[original_url] = []
                unique_urls.append(original_url)

        if unique_urls:
            with ThreadPoolExecutor(max_workers=max_concurrency) as executor:
                list(executor.map(get_new_url, unique_urls))

        def process_image(match):
            img_tag = match.group(0)
            src_match = re.search(src_pattern, img_tag)
            if not src_match:
                return match.start(), match.end(), img_tag, None

            original_url = src_match.group(1)
            new_url = src_url_to_new_url.get(original_url)
            if new_url:
                new_tag = img_tag.replace(original_url, new_url)
                return match.start(), match.end(), new_tag, original_url

            return match.start(), match.end(), img_tag, None

        results = []
        with ThreadPoolExecutor(max_workers=max_concurrency) as executor:
            futures = {
                executor.submit(process_image, match): i
                for i, match in enumerate(matches)
            }

            for i, future in enumerate(as_completed(futures)):
                result = future.result()
                results.append(result)
                if progress_callback:
                    progress_callback(i + 1, total, f"🔄 处理 {i + 1}/{total}")

        results.sort(key=lambda x: x[0])

        result_parts = []
        last_end = 0

        for start, end, tag, original_url in results:
            if original_url:
                if progress_callback:
                    progress_callback(
                        results.index((start, end, tag, original_url)) + 1,
                        total,
                        f"🔄 替换: {original_url[:50]}...",
                    )
            result_parts.append(content[last_end:start])
            result_parts.append(tag)
            last_end = end

        result_parts.append(content[last_end:])
        result_content = "".join(result_parts)

    if file_path and (inplace or output_path):
        write_path = output_path if output_path else file_path
        with open(write_path, "w", encoding="utf-8") as f:
            f.write(result_content)

    return result_content


def parse_article_metadata(file_path: str) -> dict:
    """
    解析HTML文件头中的文章元数据

    支持的文件头格式：
    <!--
    title：文章标题
    author：作者名
    cover_url：封面图片URL
    digest：摘要
    is_xiaoqiangclub：true
    thumb_media_id：封面图片media_id
    cover_local_path：本地封面图片路径
    -->

    :param file_path: HTML文件路径
    :return: 包含文章元数据的字典，字段包括：
             - title: 标题
             - author: 作者
             - cover_url: 封面图片URL
             - digest: 摘要
             - is_xiaoqiangclub: 是否使用XiaoqiangClub账号
             - thumb_media_id: 封面图片media_id
             - cover_local_path: 本地封面图片路径
    :raises FileNotFoundError: 文件不存在
    :raises ValueError: 文件格式错误，缺少必要的元数据
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"文件不存在: {file_path}")

    with open(file_path, "r", encoding="utf-8") as f:
        content = f.read()

    match = re.match(r"<!--\s*([\s\S]*?)\s*-->", content, re.MULTILINE)
    if not match:
        raise ValueError(f"文件不包含HTML注释头: {file_path}")

    header_block = match.group(1)

    metadata = {}
    patterns = {
        "title": r"title[：:]?\s*(.+?)(?:\n|$)",
        "author": r"author[：:]?\s*(.+?)(?:\n|$)",
        "cover_url": r"cover_url[：:]?\s*(.+?)(?:\n|$)",
        "digest": r"digest[：:]?\s*(.+?)(?:\n|$)",
        "is_xiaoqiangclub": r"is_xiaoqiangclub[：:]?\s*(.+?)(?:\n|$)",
        "thumb_media_id": r"thumb_media_id[：:]?\s*(.+?)(?:\n|$)",
        "cover_local_path": r"cover_local_path[：:]?\s*(.+?)(?:\n|$)",
    }

    for key, pattern in patterns.items():
        m = re.search(pattern, header_block, re.IGNORECASE)
        if m:
            value = m.group(1).strip()
            if key == "is_xiaoqiangclub":
                metadata[key] = value.lower() in ("true", "1", "yes")
            else:
                metadata[key] = value

    required_fields = ["title"]
    for field in required_fields:
        if field not in metadata or not metadata[field]:
            raise ValueError(f"文件缺少必要的元数据字段 '{field}': {file_path}")

    return metadata


def extract_content_from_html(file_path: str) -> str:
    """
    从HTML文件中提取正文内容（去除注释头）

    :param file_path: HTML文件路径
    :return: 去除注释头后的HTML内容
    :raises FileNotFoundError: 文件不存在
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"文件不存在: {file_path}")

    with open(file_path, "r", encoding="utf-8") as f:
        content = f.read()

    content = re.sub(r"<!--[\s\S]*?-->\s*", "", content, count=1)

    return content


__all__ = [
    "remove_html_comments",
    "extract_image_tags",
    "extract_image_urls",
    "replace_image_links",
    "parse_article_metadata",
    "extract_content_from_html",
]
