import random
import re
from typing import Optional


# ── 版本范围 ──────────────────────────────────────────────────────────────────
_CHROME_MAJOR_RANGE = (120, 146)
_FIREFOX_MAJOR_RANGE = (120, 126)
_EDGE_MAJOR_RANGE = (120, 146)   # Edge 基于 Chromium，版本号同步

# ── 桌面平台 OS 信息 ──────────────────────────────────────────────────────────
_WINDOWS = [
    'Windows NT 10.0; Win64; x64',
    'Windows NT 10.0; WOW64',
]

_MACOS = [
    'Macintosh; Intel Mac OS X 10_15_7',
    'Macintosh; Intel Mac OS X 11_0_0',
    'Macintosh; Intel Mac OS X 12_0_0',
    'Macintosh; Intel Mac OS X 13_0_0',
    'Macintosh; Intel Mac OS X 14_0_0',
    'Macintosh; Intel Mac OS X 15_0_0',
]

_LINUX = [
    'X11; Linux x86_64',
    'X11; Linux aarch64',
    'X11; Ubuntu; Linux x86_64',
    'X11; Fedora; Linux x86_64',
]

# ── 移动端平台 OS 信息 ─────────────────────────────────────────────────────────
_ANDROID = [
    'Linux; Android 10; K',
    'Linux; Android 11; K',
    'Linux; Android 12; K',
    'Linux; Android 13; K',
    'Linux; Android 14; K',
]

_IOS = [
    'iPhone; CPU iPhone OS 16_0 like Mac OS X',
    'iPhone; CPU iPhone OS 16_6 like Mac OS X',
    'iPhone; CPU iPhone OS 17_0 like Mac OS X',
    'iPhone; CPU iPhone OS 17_2 like Mac OS X',
    'iPhone; CPU iPhone OS 17_4 like Mac OS X',
    'iPad; CPU OS 16_0 like Mac OS X',
    'iPad; CPU OS 17_0 like Mac OS X',
    'iPad; CPU OS 17_2 like Mac OS X',
]

_DESKTOP_PLATFORMS = ('windows', 'macos', 'linux')
_MOBILE_PLATFORMS = ('android', 'ios')
_ALL_PLATFORMS = _DESKTOP_PLATFORMS + _MOBILE_PLATFORMS


def _random_chrome_ver(major_range: tuple[int, int] = _CHROME_MAJOR_RANGE) -> tuple[str, int]:
    major = random.randint(*major_range)
    build = random.randint(0, 9999)
    patch = random.randint(0, 200)
    return f'{major}.0.{build}.{patch}', major


def _pick_os(platform: str) -> str:
    mapping = {
        'windows': _WINDOWS,
        'macos': _MACOS,
        'linux': _LINUX,
        'android': _ANDROID,
        'ios': _IOS,
    }
    return random.choice(mapping[platform])


# ── 各浏览器 UA 生成 ───────────────────────────────────────────────────────────

def _chrome_ua(platform: str) -> str:
    ver, major = _random_chrome_ver()
    chrome_token = f'Chrome/{ver}'
    if platform == 'android':
        os_info = _pick_os('android')
        return (f'Mozilla/5.0 ({os_info}) '
                f'AppleWebKit/537.36 (KHTML, like Gecko) '
                f'{chrome_token} Mobile Safari/537.36')
    if platform == 'ios':
        os_info = _pick_os('ios')
        wk_minor = random.randint(1, 9)
        wk_ver = f'604.{wk_minor}.{wk_minor}'
        return (f'Mozilla/5.0 ({os_info}) '
                f'AppleWebKit/{wk_ver} (KHTML, like Gecko) '
                f'CriOS/{ver} Mobile/15E148 Safari/{wk_ver}')
    os_info = _pick_os(platform)
    return (f'Mozilla/5.0 ({os_info}) '
            f'AppleWebKit/537.36 (KHTML, like Gecko) '
            f'{chrome_token} Safari/537.36')


def _firefox_ua(platform: str) -> str:
    if platform in _MOBILE_PLATFORMS:
        platform = 'linux'
    major = random.randint(*_FIREFOX_MAJOR_RANGE)
    os_info = _pick_os(platform)
    return (f'Mozilla/5.0 ({os_info}; rv:{major}.0) '
            f'Gecko/20100101 Firefox/{major}.0')


def _edge_ua(platform: str) -> str:
    ver, major = _random_chrome_ver(_EDGE_MAJOR_RANGE)
    chrome_token = f'Chrome/{ver}'
    if platform == 'android':
        os_info = _pick_os('android')
        return (f'Mozilla/5.0 ({os_info}) '
                f'AppleWebKit/537.36 (KHTML, like Gecko) '
                f'{chrome_token} Mobile Safari/537.36 '
                f'EdgA/{ver}')
    if platform == 'ios':
        os_info = _pick_os('ios')
        return (f'Mozilla/5.0 ({os_info}) '
                f'AppleWebKit/605.1.15 (KHTML, like Gecko) '
                f'Version/17.0 EdgiOS/{ver} Mobile/15E148 Safari/604.1')
    os_info = _pick_os(platform)
    return (f'Mozilla/5.0 ({os_info}) '
            f'AppleWebKit/537.36 (KHTML, like Gecko) '
            f'{chrome_token} Safari/537.36 Edg/{ver}')


def _safari_ua(platform: str) -> str:
    if platform not in ('macos', 'ios'):
        platform = random.choice(('macos', 'ios'))
    safari_ver = random.choice(('16.0', '16.6', '17.0', '17.2', '17.4'))
    if platform == 'ios':
        os_info = _pick_os('ios')
        wk_ver = random.choice(('604.1', '605.1.15'))
        mobile_id = f'{random.randint(15,18)}E{random.randint(100, 999)}'
        return (f'Mozilla/5.0 ({os_info}) '
                f'AppleWebKit/{wk_ver} (KHTML, like Gecko) '
                f'Version/{safari_ver} Mobile/{mobile_id} Safari/{wk_ver}')
    os_info = _pick_os('macos')
    wk_ver = random.choice(('605.1.15', '606.1.36.1', '537.36'))
    return (f'Mozilla/5.0 ({os_info}) '
            f'AppleWebKit/{wk_ver} (KHTML, like Gecko) '
            f'Version/{safari_ver} Safari/{wk_ver}')


# ── 公开接口 ───────────────────────────────────────────────────────────────────

def get_ua(browser: str = 'chrome', platform: str = 'random') -> str:
    """动态生成随机 User-Agent 字符串。

    Args:
        browser:  浏览器类型，可选 ``'chrome'`` / ``'firefox'`` / ``'edge'`` /
                  ``'safari'`` / ``'random'``，默认 ``'chrome'``。
        platform: 平台类型，可选 ``'windows'`` / ``'macos'`` / ``'linux'`` /
                  ``'android'`` / ``'ios'`` / ``'random'``，默认 ``'random'``。
                  注意：``safari`` 仅支持 ``macos``/``ios``；
                  ``firefox`` 不支持移动端（自动回退到 linux）。

    Returns:
        str: 生成的 User-Agent 字符串。

    Examples:
        >>> ua = get_ua()
        >>> ua = get_ua('firefox', 'windows')
        >>> ua = get_ua('safari', 'ios')
    """
    valid_browsers = ('chrome', 'firefox', 'edge', 'safari', 'random')
    valid_platforms = _ALL_PLATFORMS + ('random',)
    if browser not in valid_browsers:
        raise ValueError(f'browser 须为 {valid_browsers} 之一，当前值: {browser!r}')
    if platform not in valid_platforms:
        raise ValueError(f'platform 须为 {valid_platforms} 之一，当前值: {platform!r}')

    if browser == 'random':
        browser = random.choice(('chrome', 'firefox', 'edge', 'safari'))

    if platform == 'random':
        if browser == 'safari':
            platform = random.choice(('macos', 'ios'))
        elif browser == 'firefox':
            platform = random.choice(_DESKTOP_PLATFORMS)
        else:
            platform = random.choice(_ALL_PLATFORMS)

    dispatch = {
        'chrome': _chrome_ua,
        'firefox': _firefox_ua,
        'edge': _edge_ua,
        'safari': _safari_ua,
    }
    return dispatch[browser](platform)



def get_headers(
    ua: Optional[str] = None,
    browser: str = 'chrome',
    platform: str = 'random',
    referer: Optional[str] = None,
    origin: Optional[str] = None,
    accept_language: str = 'zh-CN,zh;q=0.9,en;q=0.8',
    extra: Optional[dict] = None,
) -> dict:
    """生成一套完整的 HTTP 请求头。

    Args:
        ua:             指定 UA 字符串；为 ``None`` 时自动调用 :func:`get_ua` 生成。
        browser:        自动生成时使用的浏览器类型。
        platform:       自动生成时使用的平台类型。
        referer:        ``Referer`` 头的值，可选。
        origin:         ``Origin`` 头的值，可选。
        accept_language: ``Accept-Language`` 头，默认中文。
        extra:          额外追加或覆盖的请求头字典，可选。

    Returns:
        dict: 请求头字典，可直接传入 ``requests`` / ``httpx`` 等库。
    """
    if ua is None:
        ua = get_ua(browser=browser, platform=platform)
    headers = {
        'User-Agent': ua,
        'Accept': (
            'text/html,application/xhtml+xml,application/xml;'
            'q=0.9,image/avif,image/webp,image/apng,*/*;'
            'q=0.8,application/signed-exchange;v=b3;q=0.7'
        ),
        'Accept-Language': accept_language,
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Upgrade-Insecure-Requests': '1',
        'Sec-Fetch-Dest': 'document',
        'Sec-Fetch-Mode': 'navigate',
        'Sec-Fetch-Site': 'none',
        'Sec-Fetch-User': '?1',
        'Cache-Control': 'max-age=0',
    }
    if referer:
        headers['Referer'] = referer
    if origin:
        headers['Origin'] = origin
    if extra:
        headers.update(extra)
    return headers


def parse_ua(ua: str) -> dict:
    """简单解析 UA 字符串，提取关键信息。

    Args:
        ua: User-Agent 字符串。

    Returns:
        dict，包含以下字段：

        - ``browser``   (str)  : 浏览器名称，如 ``'chrome'``、``'firefox'`` 等。
        - ``version``   (str)  : 浏览器版本号字符串。
        - ``platform``  (str)  : 操作系统平台，如 ``'windows'``、``'macos'`` 等。
        - ``is_mobile`` (bool) : 是否为移动端。
        - ``raw``       (str)  : 原始 UA 字符串。
    """
    result: dict = {
        'browser': 'unknown',
        'version': 'unknown',
        'platform': 'unknown',
        'is_mobile': False,
        'raw': ua,
    }
    ua_lower = ua.lower()

    # 移动端判断
    if any(kw in ua_lower for kw in ('mobile', 'android', 'iphone', 'ipad', 'ipod')):
        result['is_mobile'] = True

    # 平台
    if 'windows' in ua_lower:
        result['platform'] = 'windows'
    elif 'macintosh' in ua_lower or 'mac os x' in ua_lower:
        result['platform'] = 'macos'
    elif 'android' in ua_lower:
        result['platform'] = 'android'
    elif 'iphone' in ua_lower or 'ipad' in ua_lower or 'ipod' in ua_lower:
        result['platform'] = 'ios'
    elif 'linux' in ua_lower:
        result['platform'] = 'linux'

    # 浏览器与版本（顺序敏感：Edge > CriOS > Chrome > Firefox > Safari > IE）
    def _extract(pattern: str) -> str:
        m = re.search(pattern, ua)
        return m.group(1) if m else 'unknown'

    if re.search(r'Edg[A-Za-z]*/', ua):
        result['browser'] = 'edge'
        result['version'] = _extract(r'Edg[A-Za-z]*/(\S+)')
    elif 'CriOS/' in ua:
        result['browser'] = 'chrome'
        result['version'] = _extract(r'CriOS/(\S+)')
    elif 'Chrome/' in ua:
        result['browser'] = 'chrome'
        result['version'] = _extract(r'Chrome/(\S+)')
    elif 'Firefox/' in ua:
        result['browser'] = 'firefox'
        result['version'] = _extract(r'Firefox/(\S+)')
    elif 'Version/' in ua and 'Safari/' in ua:
        result['browser'] = 'safari'
        result['version'] = _extract(r'Version/(\S+)')
    elif re.search(r'MSIE|Trident/', ua):
        result['browser'] = 'ie'
        m = re.search(r'MSIE (\S+)', ua) or re.search(r'rv:(\S+)', ua)
        result['version'] = m.group(1) if m else 'unknown'

    return result


class UAPool:
    """UA 对象池，适用于需要批量轮换 UA 的爬虫场景。

    Examples:
        >>> pool = UAPool(size=30, browser='chrome', platform='windows')
        >>> ua = pool.next()      # 顺序轮换
        >>> ua = pool.random()    # 随机取一条
        >>> for ua in pool:       # 遍历
        ...     print(ua)
    """

    def __init__(
        self,
        size: int = 20,
        browser: str = 'random',
        platform: str = 'random',
    ) -> None:
        self._browser = browser
        self._platform = platform
        self._pool: list[str] = []
        self._index: int = 0
        self.refresh(size=size)

    def next(self) -> str:
        """顺序取下一条 UA（到末尾后循环）。"""
        ua = self._pool[self._index % len(self._pool)]
        self._index += 1
        return ua

    def random(self) -> str:
        """随机取一条 UA。"""
        return random.choice(self._pool)

    def refresh(
        self,
        size: Optional[int] = None,
        browser: Optional[str] = None,
        platform: Optional[str] = None,
    ) -> None:
        """重新生成池中的 UA。

        Args:
            size:     新的池大小；为 ``None`` 时保持原大小。
            browser:  新的浏览器类型；为 ``None`` 时使用初始化时的值。
            platform: 新的平台类型；为 ``None`` 时使用初始化时的值。
        """
        size = size or len(self._pool) or 20
        browser = browser or self._browser
        platform = platform or self._platform
        self._pool = [get_ua(browser=browser, platform=platform) for _ in range(size)]
        self._index = 0

    def __len__(self) -> int:
        return len(self._pool)

    def __iter__(self):
        return iter(self._pool)

    def __repr__(self) -> str:
        return (f'UAPool(size={len(self._pool)}, '
                f'browser={self._browser!r}, platform={self._platform!r})')


if __name__ == '__main__':
    print('── 动态生成 ─────────────────────────────────')
    print(get_ua())
    print(get_ua('firefox', 'windows'))
    print(get_ua('edge', 'macos'))
    print(get_ua('safari', 'ios'))
    print(get_ua('chrome', 'android'))
    print(get_ua('random', 'random'))

    print('\n── 生成完整请求头 ──────────────────────────────')
    import json
    print(json.dumps(get_headers(browser='chrome', platform='windows'), indent=2, ensure_ascii=False))

    print('\n── 解析 UA ────────────────────────────────────')
    sample = get_ua('edge', 'windows')
    print(f'UA  : {sample}')
    print(f'解析: {parse_ua(sample)}')

    print('\n── UAPool ────────────────────────────────────')
    pool = UAPool(size=5, browser='chrome', platform='windows')
    print(pool)
    for u in pool:
        print(' ', u)
    print('next():', pool.next())
    print('random():', pool.random())
