import os
import time
import json
import hmac
import hashlib
import random
from typing import Any, Dict, List, Optional
from urllib.parse import quote

import requests
import urllib.parse
import urllib3


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


BASE_URL = "https://we.51job.com/api/job/search-pc"
API_BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:9999")
SIGN_KEY = "abfc8f9dcf8c3f3d8aa294ac5f2cf2cc7767e5592590f39c3f503271dd68562b"


def _now_ts() -> str:
    """返回当前时间戳字符串"""
    return str(int(time.time()))


def _build_proxy() -> Dict[str, str]:
    """构造HTTP/HTTPS代理配置"""
    url = os.getenv("PROXY_URL", "http://t13319619426654:ln8aj9nl@s432.kdltps.com:15818")
    return {"http": url, "https": url}


def _hmac_sign_url(url: str) -> str:
    """对完整URL进行HMAC-SHA256签名"""
    return hmac.new(SIGN_KEY.encode("utf-8"), url.encode("utf-8"), hashlib.sha256).hexdigest()


def _generate_acw_sc_v2(arg1: str) -> str:
    """依据arg1生成acw_sc__v2值"""
    if not arg1:
        return ""
    pos_list = [15, 35, 29, 24, 33, 16, 1, 38, 10, 9, 19, 31, 40, 27, 22, 23, 25, 13, 6, 11, 39, 18, 20, 8, 14, 21,
                32, 26, 2, 30, 7, 4, 17, 5, 3, 28, 34, 37, 12, 36]
    mask = "3000176000856006061501533003690027800375"
    out = [None] * len(pos_list)
    for i in range(len(arg1)):
        for j in range(len(pos_list)):
            if pos_list[j] == i + 1:
                out[j] = arg1[i]
                break
    arg2 = ''.join([c for c in out if c])
    arg3 = ""
    for i in range(0, min(len(arg2), len(mask)), 2):
        sc = int(arg2[i:i + 2], 16)
        mc = int(mask[i:i + 2], 16)
        arg3 += format(sc ^ mc, '02x')
    return arg3


def _build_web_headers(keyword: str, did: str, sign: str) -> Dict[str, str]:
    """构造网页接口所需请求头"""
    encoded_kw = quote(keyword, safe="")
    referer = f"https://we.51job.com/pc/search?jobArea=020000&keyword={encoded_kw}&searchType=2&keywordType="
    web_prop = {
        "partner": "",
        "webId": 2,
        "fromdomain": "51job_web",
        "frompageUrl": "https://we.51job.com/",
        "pageUrl": referer,
        "identityType": "",
        "userType": "",
        "isLogin": "否",
        "accountid": "",
        "keywordType": "直接输入",
    }
    return {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
        "Accept": "application/json, text/plain, */*",
        "accept-language": "zh-CN,zh;q=0.9",
        "from-domain": "51job_web",
        "priority": "u=1, i",
        "property": quote(json.dumps(web_prop, ensure_ascii=False), safe=""),
        "referer": referer,
        "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="136", "Google Chrome";v="136"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"macOS"',
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "sign": sign,
        "uuid": did,
        "Cookie": f"guid={did}",
    }


def _build_query(city_code: str, page: int, keyword: str) -> str:
    """构造search-pc查询串"""
    params = {
        'api_key': '51job',
        'timestamp': _now_ts(),
        'keyword': keyword,
        'searchType': '2',
        'function': '',
        'industry': '',
        'jobArea': city_code,
        'jobArea2': '',
        'landmark': '',
        'metro': '',
        'salary': '',
        'workYear': '',
        'degree': '',
        'companyType': '',
        'companySize': '',
        'jobType': '',
        'issueDate': '',
        'sortType': '0',
        'pageNum': str(page),
        'requestId': '',
        'pageSize': '20',
        'source': '1',
        'accountId': '',
        'pageCode': 'sou|sou|soulb',
        'scene': '7',
    }
    return urllib.parse.urlencode(params, quote_via=urllib.parse.quote)


def _prefetch_acw(city_code: str, page: int, keyword: str, proxies: Dict[str, str]) -> Optional[str]:
    """预取arg1并生成acw_sc__v2"""
    qs = _build_query(city_code, page, keyword)
    full_url = f"{BASE_URL}?{qs}"
    did = str(random.randint(10**15, 10**16-1))
    sign = _hmac_sign_url(full_url)
    headers = _build_web_headers(keyword, did, sign)
    try:
        resp = requests.get(full_url, headers=headers, timeout=20, proxies=proxies, verify=False)
        txt = resp.text or ""
        m = re_search_arg1(txt)
        if not m:
            return None
        return _generate_acw_sc_v2(m)
    except Exception:
        return None


def re_search_arg1(text: str) -> Optional[str]:
    """从文本中提取arg1"""
    import re
    m = re.search(r"var\s+arg1\s*=\s*['\"]([^'\"]+)['\"]", text)
    return m.group(1) if m else None


def fetch_page(city_code: str, keyword: str, page: int, proxies: Dict[str, str]) -> Dict[str, Any]:
    """抓取单页数据并返回解析后的JSON或文本封装"""
    qs = _build_query(city_code, page, keyword)
    full_url = f"{BASE_URL}?{qs}"
    did = str(random.randint(10**15, 10**16-1))
    sign = _hmac_sign_url(full_url)
    headers = _build_web_headers(keyword, did, sign)
    acw = _prefetch_acw(city_code, page, keyword, proxies)
    if acw:
        headers["Cookie"] = headers.get("Cookie", "") + f"; acw_sc__v2={acw}"
    resp = requests.get(full_url, headers=headers, timeout=30, proxies=proxies, verify=False)
    try:
        return resp.json()
    except ValueError:
        return {"raw": resp.text}


def load_company_keywords() -> List[str]:
    """读取同目录company.txt为关键词列表"""
    fp = os.path.join(os.path.dirname(__file__), "company.txt")
    try:
        with open(fp, "r", encoding="utf-8") as f:
            lines = [ln.strip() for ln in f.readlines()]
            return [ln for ln in lines if ln]
    except Exception:
        return []


def fetch_service_params() -> Optional[Dict[str, Any]]:
    """从服务端获取当天未使用的检索条件并占用

    返回:
        dict: {"city_code": str, "keyword": str} 或 None
    """
    try:
        url = f"{API_BASE_URL}/api/v1/keyword/available"
        r = requests.get(url, params={"source": "qcwy", "limit": 1}, timeout=10)
        if r.status_code != 200:
            return None
        js = r.json()
        data = js.get("data") or {}
        items = data.get("items") or []
        if not items:
            return None
        item = items[0]
        ids = [item.get("id")]
        if ids and ids[0]:
            try:
                murl = f"{API_BASE_URL}/api/v1/keyword/mark-used"
                requests.post(murl, json={"source": "qcwy", "ids": ids}, timeout=10)
            except Exception:
                pass
        return {"city_code": str(item.get("city", "")), "keyword": str(item.get("job", ""))}
    except Exception:
        return None


def run(city_code: str = "020000", max_pages: int = 3) -> None:
    """执行基于company.txt的固定条件搜索并输出返回数据"""
    proxies = _build_proxy()
    # 优先从服务端拉取城市与关键词；无数据时回退到本地company.txt
    svc = fetch_service_params()
    keywords = [svc["keyword"]] if svc else load_company_keywords()
    city_code = svc["city_code"] if svc else city_code
    for kw in keywords:
        for p in range(1, max_pages + 1):
            data = fetch_page(city_code, kw, p, proxies)
            payload = {
                "keyword": kw,
                "page": p,
                "data": data,
            }
            print(json.dumps(payload, ensure_ascii=False))
            time.sleep(random.uniform(0.8, 1.6))


if __name__ == "__main__":
    run()
