"""东财接口 curl 桥接。

东财 WAF 拒绝 Python requests 的 TLS 指纹（表现为 RemoteDisconnected），但系统
curl 不受影响（2026-09 实测：requests 全线被拒、curl 正常）。本模块用 subprocess
调 curl 完成东财 HTTP GET，返回已解析的 JSON，供每日扫描流水线使用。

域名容灾：push2 与 push2delay 互为备份（东财对单一域名高频访问会临时限流，
返回 Empty reply；delay 域名承载同样的数据接口）。
"""
import json
import re
import subprocess
import time
import urllib.parse

DEFAULT_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'

_HOST_SWAPS = [
    ('push2.eastmoney.com', 'push2delay.eastmoney.com'),
    ('push2delay.eastmoney.com', 'push2.eastmoney.com'),
    ('80.push2.eastmoney.com', 'push2delay.eastmoney.com'),
]


def _curl_once(url, timeout, headers):
    cmd = ['curl', '-sSL', '-m', str(timeout), '-A', DEFAULT_UA, url]
    if headers:
        for k, v in headers.items():
            cmd += ['-H', f'{k}: {v}']
    else:
        cmd += ['-H', 'Referer: https://finance.eastmoney.com/']
    try:
        p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 5)
    except subprocess.TimeoutExpired:
        return None
    out = (p.stdout or '').strip()
    if not out:
        return None
    try:
        return json.loads(out)
    except ValueError:
        return None


def curl_get_json(url, params=None, timeout=15, headers=None):
    if params:
        qs = urllib.parse.urlencode(params)
        url = f"{url}{'&' if '?' in url else '?'}{qs}"
    if url.startswith('http://'):
        url = 'https://' + url[len('http://'):]
    candidates = [url]
    for old, new in _HOST_SWAPS:
        if old in url:
            candidates.append(url.replace(old, new))
            break
    last_err = None
    for u in candidates:
        data = _curl_once(u, timeout, headers)
        if data is not None:
            return data
        last_err = u
        # 东财按请求频率限流（Empty reply），切候选域名前退避
        time.sleep(0.4)
    raise RuntimeError(f'curl failed (empty/invalid response): {last_err[:120]}')


def curl_get_json_retry(url, params=None, timeout=15, headers=None, retries=2, delay=0.5):
    """带重试版本：所有域名候选都失败后整体重试。"""
    import time
    last_exc = None
    for i in range(retries):
        try:
            return curl_get_json(url, params=params, timeout=timeout, headers=headers)
        except RuntimeError as e:
            last_exc = e
            if i < retries - 1:
                time.sleep(delay)
    raise last_exc


def tencent_daily_kline(code, days=300, max_count=640):
    """腾讯日K备援：东财 push2his 被限流时使用（腾讯不限制 Python requests）。

    code 为6位股票代码或带前缀指数（如 'sh000001'）。
    返回 records 列表（与东财解析格式对齐）：
    {date, open, close, high, low, volume, amount}
    腾讯不提供成交额，amount 用 volume*close 近似。
    失败返回 None。
    """
    import requests
    from datetime import datetime, timedelta
    if re.match(r'^(sh|sz|bj)[0-9]+$', code):
        tx_code = code
    else:
        prefix = 'sh' if code.startswith(('6', '9', '5')) else 'sz'
        tx_code = f'{prefix}{code}'
    end = datetime.now().strftime('%Y-%m-%d')
    start = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
    url = f'https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={tx_code},day,{start},{end},{max_count},qfq'
    try:
        r = requests.get(url, headers={'User-Agent': DEFAULT_UA}, timeout=10)
        d = r.json().get('data') or {}
        klines = (d.get(tx_code) or {}).get('qfqday') or (d.get(tx_code) or {}).get('day') or []
    except Exception:
        return None
    records = []
    for k in klines:
        if len(k) < 6:
            continue
        try:
            o, c, h, l, v = float(k[1]), float(k[2]), float(k[3]), float(k[4]), float(k[5])
        except (ValueError, TypeError):
            continue
        records.append({
            'date': k[0], 'open': o, 'close': c, 'high': h, 'low': l,
            'volume': v, 'amount': round(v * c, 2),
        })
    return records if records else None


def sina_daily_kline(code, count=90):
    """新浪日K备援：东财push2his与腾讯K线均被限流时使用。

    code 为6位股票代码或带前缀（如 'sz002497'）。
    新浪K线为不复权数据（scale=240 日线）。
    返回 records 列表：{date, open, close, high, low, volume, amount}
    失败返回 None。
    """
    import requests
    if re.match(r'^(sh|sz|bj)[0-9]+$', code):
        sn_code = code
    else:
        prefix = 'sh' if code.startswith(('6', '9', '5')) else 'sz'
        sn_code = f'{prefix}{code}'
    url = 'https://quotes.sina.cn/cn/api/json_v2.php/CN_MarketDataService.getKLineData'
    params = {'symbol': sn_code, 'scale': 240, 'ma': 'no', 'datalen': count}
    try:
        r = requests.get(url, params=params, headers={
            'User-Agent': DEFAULT_UA, 'Referer': 'https://finance.sina.com.cn/',
        }, timeout=10)
        klines = r.json()
    except Exception:
        return None
    if not isinstance(klines, list) or not klines:
        return None
    records = []
    for k in klines:
        try:
            o, c = float(k['open']), float(k['close'])
            h, l, v = float(k['high']), float(k['low']), float(k['volume'])
        except (ValueError, TypeError, KeyError):
            continue
        records.append({
            'date': k.get('day', ''), 'open': o, 'close': c, 'high': h, 'low': l,
            'volume': v, 'amount': round(v * c, 2),
        })
    return records if records else None
