"""
WEEX ETH-USDT 永续合约交易框架
与 bitmart/框架.py 结构对应：浏览器 + API（K线/余额/持仓）+ 市价开平仓，供具体策略复用。
"""
import time
from typing import Optional, Dict, List, Tuple

from tqdm import tqdm
from loguru import logger
from bit_tools import openBrowser
from DrissionPage import ChromiumPage, ChromiumOptions
from curl_cffi import requests


# ==================== 配置 ====================
class Config:
    TGE_URL = "http://127.0.0.1:50326"
    TGE_AUTHORIZATION = "Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91"

    CONTRACT_ID = "10000002"
    PRODUCT_CODE = "cmt_ethusdt"
    KLINE_TYPE = "MINUTE_30"
    KLINE_LIMIT = 300
    TRADING_URL = "https://www.weex.com/zh-CN/futures/ETH-USDT"

    MAX_RETRY_ATTEMPTS = 3
    RETRY_DELAY = 1


# ==================== 主框架类 ====================
class WeexFuturesTransaction:
    """WEEX 永续合约交易框架：K线、余额、持仓、浏览器、市价开平仓"""

    def __init__(self, tge_id):
        self.page: Optional[ChromiumPage] = None
        self.tge_port: Optional[int] = None
        self.tge_id = tge_id

        self.session = requests.Session()
        self.headers: Optional[Dict] = None

        self.start = 0   # 持仓: -1 空, 0 无, 1 多
        self.open_avg_price = None
        self.current_amount = None
        self.position_data: Optional[List] = None

        self.pbar = tqdm(total=30, desc="等待K线", ncols=80)
        self.last_kline_time = None

    # ------------------------- Token（请求 API 前需先取 token）-------------------------
    def _get_token(self) -> bool:
        """从浏览器请求交易页时抓取 U-TOKEN，写入 session headers"""
        if not self.page:
            return False
        tab = self.page.new_tab()
        tab.listen.start("/user/security/getLanguageType")
        try:
            for attempt in range(Config.MAX_RETRY_ATTEMPTS):
                try:
                    tab.get(url=Config.TRADING_URL)
                    res = tab.listen.wait(timeout=5)
                    if res.request.headers.get("U-TOKEN"):
                        self.headers = dict(res.request.headers)
                        self.session.headers.update(self.headers)
                        logger.success("获取 token 成功")
                        return True
                except Exception as e:
                    logger.warning(f"获取 token 第 {attempt + 1} 次失败: {e}")
                    if attempt < Config.MAX_RETRY_ATTEMPTS - 1:
                        time.sleep(Config.RETRY_DELAY)
            return False
        finally:
            tab.close()

    # ------------------------- API -------------------------
    def get_klines(
        self,
        contract_id: str = Config.CONTRACT_ID,
        product_code: str = Config.PRODUCT_CODE,
        kline_type: str = Config.KLINE_TYPE,
        limit: int = Config.KLINE_LIMIT,
    ) -> Optional[List[Dict]]:
        """获取 K 线，返回 [{'id', 'open', 'high', 'low', 'close'}, ...]，按 id 升序。请求前需已取 token。"""
        # if not self._get_token():
        #     logger.error("获取 token 失败，无法拉取 K 线")
        #     return None
        params = {
            "contractId": contract_id,
            "productCode": product_code,
            "priceType": "LAST_PRICE",
            "klineType": kline_type,
            "limit": str(limit),
            "timeZone": "string",
            "languageType": "1",
            "sign": "SIGN",
        }
        for attempt in range(Config.MAX_RETRY_ATTEMPTS):
            try:
                response = self.session.get(
                    "https://http-gateway2.elconvo.com/api/v1/public/quote/v1/getKlineV2",
                    params=params,
                    timeout=15,
                )
                if response.status_code != 200:
                    if attempt < Config.MAX_RETRY_ATTEMPTS - 1:
                        time.sleep(Config.RETRY_DELAY)
                    continue
                data = response.json()
                if "data" not in data or "dataList" not in data["data"]:
                    continue
                result = data["data"]["dataList"]
                kline_data = []
                for item in result:
                    # [close, high, low, open, id]
                    kline_data.append({
                        "id": int(item[4]),
                        "open": float(item[3]),
                        "high": float(item[1]),
                        "low": float(item[2]),
                        "close": float(item[0]),
                    })
                kline_data.sort(key=lambda x: x["id"])
                return kline_data
            except Exception as e:
                logger.error(f"获取 K 线异常: {e}")
                if attempt < Config.MAX_RETRY_ATTEMPTS - 1:
                    time.sleep(Config.RETRY_DELAY)
        return None

    def get_current_price(self) -> Optional[float]:
        """用最近一根 K 线收盘价作为当前价"""
        klines = self.get_klines(limit=3)
        if klines:
            return float(klines[-1]["close"])
        return None

    def get_available_balance(self) -> Optional[float]:
        """合约账户可用余额"""
        if not self._get_token():
            return None
        for attempt in range(Config.MAX_RETRY_ATTEMPTS):
            try:
                response = self.session.post(
                    "https://gateway2.ngsvsfx.cn/v1/gw/assetsWithBalance/new",
                    timeout=15,
                )
                return float(response.json()["data"]["newContract"]["balanceList"][0]["available"])
            except Exception as e:
                logger.error(f"获取余额异常: {e}")
                if attempt < Config.MAX_RETRY_ATTEMPTS - 1:
                    time.sleep(Config.RETRY_DELAY)
        return None

    def get_position_status(self) -> bool:
        """从成交历史解析持仓方向，更新 self.start (-1/0/1)，成功返回 True"""
        # if not self._get_token():
        #     return False
        payload = {
            "filterContractIdList": [10000002],
            "limit": 100,
            "languageType": 0,
            "sign": "SIGN",
            "timeZone": "string",
        }
        for attempt in range(Config.MAX_RETRY_ATTEMPTS):
            try:
                response = self.session.post(
                    "https://http-gateway2.janapw.com/api/v1/private/order/v2/getHistoryOrderFillTransactionPage",
                    json=payload,
                    timeout=15,
                )
                datas = response.json().get("data", {}).get("dataList")
                self.position_data = datas or None
                if not datas:
                    self.start = 0
                    return True
                rev = list(datas)
                rev.reverse()
                start, start1 = 0, 0
                for i in rev:
                    d = i.get("legacyOrderDirection")
                    if d == "CLOSE_SHORT":
                        start = 0
                    elif d == "CLOSE_LONG":
                        start1 = 0
                    elif d == "OPEN_SHORT":
                        start -= 1
                    elif d == "OPEN_LONG":
                        start1 += 1
                if start1:
                    self.start = 1
                elif start:
                    self.start = -1
                else:
                    self.start = 0
                return True
            except Exception as e:
                logger.error(f"获取持仓异常: {e}")
                if attempt < Config.MAX_RETRY_ATTEMPTS - 1:
                    time.sleep(Config.RETRY_DELAY)
        return False

    # ------------------------- 浏览器 -------------------------
    def openBrowser(self) -> bool:
        """打开 TGE 对应浏览器实例"""
        try:
            bit_port = openBrowser(id=self.tge_id)
            co = ChromiumOptions()
            co.set_local_port(port=bit_port)
            self.page = ChromiumPage(addr_or_opts=co)
            self.tge_port = bit_port
            return True
        except Exception:
            return False

    def take_over_browser(self) -> bool:
        """接管已有浏览器"""
        if not self.tge_port:
            return False
        try:
            co = ChromiumOptions()
            co.set_local_port(self.tge_port)
            self.page = ChromiumPage(addr_or_opts=co)
            self.page.set.window.max()
            return True
        except Exception:
            return False

    def close_extra_tabs(self) -> bool:
        """关闭多余标签页，只保留第一个"""
        if not self.page:
            return False
        try:
            for idx, tab in enumerate(self.page.get_tabs()):
                if idx > 0:
                    tab.close()
            return True
        except Exception:
            return False

    def click_safe(self, xpath: str, sleep: float = 0.5) -> bool:
        """安全点击"""
        try:
            ele = self.page.ele(xpath)
            if not ele:
                return False
            ele.scroll.to_see(center=True)
            time.sleep(sleep)
            ele.click(by_js=True)
            return True
        except Exception:
            return False

    # ------------------------- 交易 -------------------------
    def 平仓(self) -> bool:
        """市价平仓（闪电平仓）"""
        try:
            self.page.ele('x:(//span[normalize-space(text()) = "闪电平仓"])').scroll.to_see(center=True)
            time.sleep(0.5)
            self.page.ele('x:(//span[normalize-space(text()) = "闪电平仓"])').click(by_js=True)
            time.sleep(2)
            logger.info("执行平仓")
            return True
        except Exception as e:
            logger.error(f"平仓异常: {e}")
            return False

    def 开单(self, marketPriceLongOrder: int = 0, size: Optional[float] = None) -> bool:
        """
        市价开仓
        marketPriceLongOrder: 1 做多, -1 做空
        size: 数量（金额）
        """
        if size is None or size <= 0:
            logger.warning("开单数量无效")
            return False
        try:
            self.click_safe('x:(//button[normalize-space(text()) = "市价"])')
            time.sleep(0.5)
            self.page.ele('x://input[@placeholder="请输入数量"]').input(size)
            time.sleep(0.5)
            if marketPriceLongOrder == -1:
                self.page.ele('x://*[normalize-space(text()) ="卖出开空"]').click(by_js=True)
            elif marketPriceLongOrder == 1:
                self.page.ele('x://*[normalize-space(text()) ="买入开多"]').click(by_js=True)
            else:
                return False
            logger.info(f"市价{'做空' if marketPriceLongOrder == -1 else '做多'} 数量={size}")
            return True
        except Exception as e:
            logger.error(f"开单异常: {e}")
            return False

    def ding(self, text: str, error: bool = False) -> None:
        """日志/通知入口，可在此接入钉钉等"""
        if error:
            logger.error(text)
        else:
            logger.info(text)

    def action(self) -> None:
        """框架入口：打开浏览器 -> 交易页 -> 取 token -> 拉 K 线 -> 切市价（示例）"""
        if not self.openBrowser():
            self.ding("打开 TGE 失败！", error=True)
            return
        logger.info("TGE 浏览器已打开")

        self.close_extra_tabs()
        self.page.get(url=Config.TRADING_URL)
        time.sleep(2)

        if not self._get_token():
            self.ding("获取 token 失败", error=True)
            return

        klines = self.get_klines()
        if klines:
            logger.info(f"获取到 {len(klines)} 根 K 线，最新收盘 {klines[-1]['close']}")

        self.click_safe('x:(//button[normalize-space(text()) = "市价"])')
        # 此处可接具体策略循环：get_klines -> 信号 -> 开单/平仓
        # self.平仓()
        self.开单(marketPriceLongOrder=1, size=100)


if __name__ == "__main__":
    WeexFuturesTransaction(tge_id="86837a981aba4576be6916a0ef6ad785").action()
