import requests
import time
from urllib.parse import urlencode, quote
from typing import Optional, Any

# 延迟导入 IP 地图模块
from . import ip_map

# 延迟导入 httpx，仅在需要 HTTP/2 时使用
_httpx = None

def _get_httpx():
    """延迟加载 httpx 模块"""
    global _httpx
    if _httpx is None:
        try:
            import httpx
            _httpx = httpx
        except ImportError:
            raise ImportError(
                "httpx is required for HTTP/2 support. "
                "Install it with: pip install httpx[http2]"
            )
    return _httpx


# 延迟导入 curl_cffi，仅在需要 TLS 指纹时使用
_curl_cffi = None

def _get_curl_cffi():
    """延迟加载 curl_cffi 模块"""
    global _curl_cffi
    if _curl_cffi is None:
        try:
            from curl_cffi import requests as curl_requests
            _curl_cffi = curl_requests
        except ImportError:
            raise ImportError(
                "curl_cffi is required for TLS fingerprint impersonation. "
                "Install it with: pip install curl_cffi"
            )
    return _curl_cffi


class CFSpiderResponse:
    def __init__(self, response, cf_colo=None, cf_ray=None):
        self._response = response
        self.cf_colo = cf_colo
        self.cf_ray = cf_ray
    
    @property
    def text(self):
        return self._response.text
    
    @property
    def content(self):
        return self._response.content
    
    @property
    def status_code(self):
        return self._response.status_code
    
    @property
    def headers(self):
        return self._response.headers
    
    @property
    def cookies(self):
        return self._response.cookies
    
    @property
    def url(self):
        return self._response.url
    
    @property
    def encoding(self):
        return self._response.encoding
    
    @encoding.setter
    def encoding(self, value):
        self._response.encoding = value
    
    def json(self, **kwargs):
        return self._response.json(**kwargs)
    
    def raise_for_status(self):
        self._response.raise_for_status()


def request(method, url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None, 
             map_output=False, map_file="cfspider_map.html", **kwargs):
    """
    发送 HTTP 请求
    
    Args:
        method: HTTP 方法
        url: 目标 URL
        cf_proxies: 代理地址（选填）
                    - 当 cf_workers=True 时，填写 CFspider Workers 地址
                    - 当 cf_workers=False 时，填写普通代理地址（如 http://127.0.0.1:8080）
        cf_workers: 是否使用 CFspider Workers API（默认 True）
                    - True: cf_proxies 为 Workers 地址，通过 Workers API 代理
                    - False: cf_proxies 为普通代理，使用 requests 的 proxies 参数
        http2: 是否启用 HTTP/2（默认 False）
               - True: 使用 httpx 客户端，支持 HTTP/2
               - False: 使用 requests 库（默认行为）
        impersonate: TLS 指纹模拟（选填）
                     - 如 "chrome131", "safari18_0", "firefox133" 等
                     - 设置后自动使用 curl_cffi 发送请求
        map_output: 是否生成 IP 地图 HTML 文件（默认 False）
        map_file: 地图输出文件名（默认 "cfspider_map.html"）
        **kwargs: 其他参数（与 requests 库一致）
    """
    params = kwargs.pop("params", None)
    headers = kwargs.pop("headers", {})
    data = kwargs.pop("data", None)
    json_data = kwargs.pop("json", None)
    cookies = kwargs.pop("cookies", None)
    timeout = kwargs.pop("timeout", 30)
    
    # 记录请求开始时间
    start_time = time.time()
    
    # 如果指定了 impersonate，使用 curl_cffi
    if impersonate:
        response = _request_impersonate(
            method, url, cf_proxies, cf_workers, impersonate,
            params=params, headers=headers, data=data,
            json_data=json_data, cookies=cookies, timeout=timeout,
            **kwargs
        )
        _handle_map_output(response, url, start_time, map_output, map_file)
        return response
    
    # 如果启用 HTTP/2，使用 httpx
    if http2:
        response = _request_httpx(
            method, url, cf_proxies, cf_workers,
            params=params, headers=headers, data=data,
            json_data=json_data, cookies=cookies, timeout=timeout,
            **kwargs
        )
        _handle_map_output(response, url, start_time, map_output, map_file)
        return response
    
    # 如果没有指定 cf_proxies，直接使用 requests
    if not cf_proxies:
        resp = requests.request(
            method,
            url,
            params=params,
            headers=headers,
            data=data,
            json=json_data,
            cookies=cookies,
            timeout=timeout,
            **kwargs
        )
        response = CFSpiderResponse(resp)
        _handle_map_output(response, url, start_time, map_output, map_file)
        return response
    
    # cf_workers=False：使用普通代理
    if not cf_workers:
        # 处理代理格式
        proxy_url = cf_proxies
        if not proxy_url.startswith(('http://', 'https://', 'socks5://')):
            proxy_url = f"http://{proxy_url}"
        
        proxies = {
            "http": proxy_url,
            "https": proxy_url
        }
        
        resp = requests.request(
            method,
            url,
            params=params,
            headers=headers,
            data=data,
            json=json_data,
            cookies=cookies,
            timeout=timeout,
            proxies=proxies,
            **kwargs
        )
        response = CFSpiderResponse(resp)
        _handle_map_output(response, url, start_time, map_output, map_file)
        return response
    
    # cf_workers=True：使用 CFspider Workers API 代理
    cf_proxies_url = cf_proxies.rstrip("/")
    
    # 确保有协议前缀
    if not cf_proxies_url.startswith(('http://', 'https://')):
        cf_proxies_url = f"https://{cf_proxies_url}"
    
    target_url = url
    if params:
        target_url = f"{url}?{urlencode(params)}"
    
    proxy_url = f"{cf_proxies_url}/proxy?url={quote(target_url, safe='')}&method={method.upper()}"
    
    request_headers = {}
    if headers:
        for key, value in headers.items():
            request_headers[f"X-CFSpider-Header-{key}"] = value
    
    if cookies:
        cookie_str = "; ".join([f"{k}={v}" for k, v in cookies.items()])
        request_headers["X-CFSpider-Header-Cookie"] = cookie_str
    
    resp = requests.post(
        proxy_url,
        headers=request_headers,
        data=data,
        json=json_data,
        timeout=timeout,
        **kwargs
    )
    
    cf_colo = resp.headers.get("X-CF-Colo")
    cf_ray = resp.headers.get("CF-Ray")
    
    response = CFSpiderResponse(resp, cf_colo=cf_colo, cf_ray=cf_ray)
    _handle_map_output(response, url, start_time, map_output, map_file)
    return response


def _handle_map_output(response, url, start_time, map_output, map_file):
    """处理 IP 地图输出"""
    if not map_output:
        return
    
    # 计算响应时间
    response_time = (time.time() - start_time) * 1000  # 毫秒
    
    # 收集 IP 记录
    ip_map.add_ip_record(
        url=url,
        ip=None,  # 无法直接获取 IP，但有 cf_colo
        cf_colo=getattr(response, 'cf_colo', None),
        cf_ray=getattr(response, 'cf_ray', None),
        status_code=response.status_code,
        response_time=response_time
    )
    
    # 生成地图 HTML
    ip_map.generate_map_html(output_file=map_file)


def _request_impersonate(method, url, cf_proxies, cf_workers, impersonate,
                         params=None, headers=None, data=None, json_data=None,
                         cookies=None, timeout=30, **kwargs):
    """使用 curl_cffi 发送请求（支持 TLS 指纹模拟）"""
    curl_requests = _get_curl_cffi()
    
    # 如果没有指定 cf_proxies，直接请求
    if not cf_proxies:
        response = curl_requests.request(
            method,
            url,
            params=params,
            headers=headers,
            data=data,
            json=json_data,
            cookies=cookies,
            timeout=timeout,
            impersonate=impersonate,
            **kwargs
        )
        return CFSpiderResponse(response)
    
    # cf_workers=False：使用普通代理
    if not cf_workers:
        proxy_url = cf_proxies
        if not proxy_url.startswith(('http://', 'https://', 'socks5://')):
            proxy_url = f"http://{proxy_url}"
        
        response = curl_requests.request(
            method,
            url,
            params=params,
            headers=headers,
            data=data,
            json=json_data,
            cookies=cookies,
            timeout=timeout,
            impersonate=impersonate,
            proxies={"http": proxy_url, "https": proxy_url},
            **kwargs
        )
        return CFSpiderResponse(response)
    
    # cf_workers=True：使用 CFspider Workers API 代理
    cf_proxies = cf_proxies.rstrip("/")
    
    if not cf_proxies.startswith(('http://', 'https://')):
        cf_proxies = f"https://{cf_proxies}"
    
    target_url = url
    if params:
        target_url = f"{url}?{urlencode(params)}"
    
    proxy_url = f"{cf_proxies}/proxy?url={quote(target_url, safe='')}&method={method.upper()}"
    
    request_headers = {}
    if headers:
        for key, value in headers.items():
            request_headers[f"X-CFSpider-Header-{key}"] = value
    
    if cookies:
        cookie_str = "; ".join([f"{k}={v}" for k, v in cookies.items()])
        request_headers["X-CFSpider-Header-Cookie"] = cookie_str
    
    response = curl_requests.post(
        proxy_url,
        headers=request_headers,
        data=data,
        json=json_data,
        timeout=timeout,
        impersonate=impersonate,
        **kwargs
    )
    
    cf_colo = response.headers.get("X-CF-Colo")
    cf_ray = response.headers.get("CF-Ray")
    
    return CFSpiderResponse(response, cf_colo=cf_colo, cf_ray=cf_ray)


def _request_httpx(method, url, cf_proxies, cf_workers, params=None, headers=None,
                   data=None, json_data=None, cookies=None, timeout=30, **kwargs):
    """使用 httpx 发送请求（支持 HTTP/2）"""
    httpx = _get_httpx()
    
    # 如果没有指定 cf_proxies，直接请求
    if not cf_proxies:
        with httpx.Client(http2=True, timeout=timeout) as client:
            response = client.request(
                method,
                url,
                params=params,
                headers=headers,
                data=data,
                json=json_data,
                cookies=cookies,
                **kwargs
            )
            return CFSpiderResponse(response)
    
    # cf_workers=False：使用普通代理
    if not cf_workers:
        proxy_url = cf_proxies
        if not proxy_url.startswith(('http://', 'https://', 'socks5://')):
            proxy_url = f"http://{proxy_url}"
        
        with httpx.Client(http2=True, timeout=timeout, proxy=proxy_url) as client:
            response = client.request(
                method,
                url,
                params=params,
                headers=headers,
                data=data,
                json=json_data,
                cookies=cookies,
                **kwargs
            )
            return CFSpiderResponse(response)
    
    # cf_workers=True：使用 CFspider Workers API 代理
    cf_proxies = cf_proxies.rstrip("/")
    
    if not cf_proxies.startswith(('http://', 'https://')):
        cf_proxies = f"https://{cf_proxies}"
    
    target_url = url
    if params:
        target_url = f"{url}?{urlencode(params)}"
    
    proxy_url = f"{cf_proxies}/proxy?url={quote(target_url, safe='')}&method={method.upper()}"
    
    request_headers = {}
    if headers:
        for key, value in headers.items():
            request_headers[f"X-CFSpider-Header-{key}"] = value
    
    if cookies:
        cookie_str = "; ".join([f"{k}={v}" for k, v in cookies.items()])
        request_headers["X-CFSpider-Header-Cookie"] = cookie_str
    
    with httpx.Client(http2=True, timeout=timeout) as client:
        response = client.post(
            proxy_url,
            headers=request_headers,
            data=data,
            json=json_data,
            **kwargs
        )
    
    cf_colo = response.headers.get("X-CF-Colo")
    cf_ray = response.headers.get("CF-Ray")
    
    return CFSpiderResponse(response, cf_colo=cf_colo, cf_ray=cf_ray)


def get(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
        map_output=False, map_file="cfspider_map.html", **kwargs):
    """
    发送 GET 请求
    
    Args:
        url: 目标 URL
        cf_proxies: 代理地址
        cf_workers: 是否使用 Workers API（默认 True）
        http2: 是否启用 HTTP/2
        impersonate: TLS 指纹（如 "chrome131", "safari18_0", "firefox133"）
        map_output: 是否生成 IP 地图 HTML 文件
        map_file: 地图输出文件名
    """
    return request("GET", url, cf_proxies=cf_proxies, cf_workers=cf_workers, 
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def post(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
         map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 POST 请求"""
    return request("POST", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def put(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
        map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 PUT 请求"""
    return request("PUT", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def delete(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
           map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 DELETE 请求"""
    return request("DELETE", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def head(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
         map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 HEAD 请求"""
    return request("HEAD", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def options(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
            map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 OPTIONS 请求"""
    return request("OPTIONS", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def patch(url, cf_proxies=None, cf_workers=True, http2=False, impersonate=None,
          map_output=False, map_file="cfspider_map.html", **kwargs):
    """发送 PATCH 请求"""
    return request("PATCH", url, cf_proxies=cf_proxies, cf_workers=cf_workers,
                   http2=http2, impersonate=impersonate,
                   map_output=map_output, map_file=map_file, **kwargs)


def clear_map_records():
    """清空 IP 地图记录"""
    ip_map.clear_records()


def get_map_collector():
    """获取 IP 地图收集器"""
    return ip_map.get_collector()
