import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _candidate_files_dirs() -> List[Path]:
    home = Path.home()
    prefixes = [
        home / "Library/Application Support/net.metaquotes.wine.metatrader5",
        home / "Library/Application Support/net.metaquotes.wine.metatrader5-beta",
        home / ".wine",
    ]
    out: List[Path] = []
    for prefix in prefixes:
        fixed = [
            prefix / "drive_c/users/user/AppData/Roaming/MetaQuotes/Terminal/Common/Files",
            prefix / f"drive_c/users/{os.getenv('USER', 'user')}/AppData/Roaming/MetaQuotes/Terminal/Common/Files",
            prefix / "drive_c/Program Files/MetaTrader 5/MQL5/Files",
        ]
        for p in fixed:
            if p.exists():
                out.append(p)
        if prefix.exists():
            for p in prefix.glob("**/MetaQuotes/Terminal/*/Common/Files"):
                if p.exists():
                    out.append(p)
    uniq: List[Path] = []
    seen = set()
    for p in out:
        s = str(p.resolve())
        if s in seen:
            continue
        seen.add(s)
        uniq.append(p)
    return uniq


def _wine_common_files_dir() -> Optional[Path]:
    env_dir = os.environ.get("EA_BRIDGE_FILES_DIR", "").strip()
    if env_dir:
        p = Path(env_dir).expanduser()
        if p.exists():
            return p
    candidates = _candidate_files_dirs()
    with_snapshot = []
    with_bridge = []
    for files in candidates:
        bridge = files / "gold_autotrade/bridge"
        snap = bridge / "market_snapshot.json"
        if snap.exists():
            with_snapshot.append((snap.stat().st_mtime, files))
        elif bridge.exists():
            with_bridge.append(files)
    if with_snapshot:
        with_snapshot.sort(key=lambda x: x[0], reverse=True)
        return with_snapshot[0][1]
    if with_bridge:
        return with_bridge[0]
    for p in candidates:
        if p.exists():
            return p
    return None


class EABridgeLiveSource:
    def __init__(self, symbol: str, timeframe: str, project_root: Optional[Path] = None) -> None:
        self.symbol = symbol
        self.timeframe = timeframe
        root = project_root or Path(__file__).resolve().parents[1]
        common = _wine_common_files_dir()
        if common is not None:
            if (common / "market_snapshot.json").exists() or (common / "to_ea").exists():
                self.snapshot_path = common / "market_snapshot.json"
            else:
                self.snapshot_path = common / "gold_autotrade/bridge/market_snapshot.json"
        else:
            self.snapshot_path = root / "data/bridge/market_snapshot.json"

    def connect(self) -> bool:
        if self.snapshot_path.exists():
            return True
        return self.snapshot_path.parent.exists()

    def close(self) -> None:
        return None

    def _ema(self, values: List[float], period: int) -> float:
        if not values:
            return 0.0
        alpha = 2.0 / (period + 1.0)
        ema = values[0]
        for v in values[1:]:
            ema = alpha * v + (1.0 - alpha) * ema
        return float(ema)

    def _sma(self, values: List[float], period: int) -> float:
        if not values:
            return 0.0
        if len(values) < period:
            return float(sum(values) / len(values))
        x = values[-period:]
        return float(sum(x) / len(x))

    def _atr(self, highs: List[float], lows: List[float], closes: List[float], period: int = 14) -> float:
        if len(closes) < 2:
            return 0.0
        tr = []
        for i in range(1, len(closes)):
            tr.append(max(highs[i] - lows[i], abs(highs[i] - closes[i - 1]), abs(lows[i] - closes[i - 1])))
        if not tr:
            return 0.0
        if len(tr) < period:
            return float(sum(tr) / len(tr))
        return float(sum(tr[-period:]) / period)

    def _percentile(self, values: List[float], p: float) -> float:
        if not values:
            return 0.0
        xs = sorted(values)
        idx = int(round((len(xs) - 1) * p))
        idx = max(0, min(len(xs) - 1, idx))
        return float(xs[idx])

    def snapshot(self) -> Optional[Dict[str, Any]]:
        if not self.snapshot_path.exists():
            return None
        try:
            payload = json.loads(self.snapshot_path.read_text(encoding="utf-8"))
        except Exception:
            return None
        tick = payload.get("tick", {})
        bars = payload.get("bars", [])
        if not isinstance(bars, list) or len(bars) < 40:
            return None
        closes = [float(x.get("close", 0)) for x in bars]
        highs = [float(x.get("high", 0)) for x in bars]
        lows = [float(x.get("low", 0)) for x in bars]
        spreads = [float(x.get("spread", 0)) for x in bars]
        bid = float(tick.get("bid", 0))
        ask = float(tick.get("ask", 0))
        if bid <= 0 or ask <= 0:
            return None
        tick_time_ms = int(tick.get("time_msc", 0))
        now_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
        market_data_delay_ms = max(0, now_ms - tick_time_ms) if tick_time_ms > 0 else 0
        close_last = closes[-1]
        mid = (bid + ask) / 2.0
        tick_bar_deviation_bps = abs(mid - close_last) / close_last * 10000 if close_last > 0 else 0.0
        ema9 = self._ema(closes[-80:], 9)
        ema21 = self._ema(closes[-120:], 21)
        macd_fast = self._ema(closes[-120:], 12)
        macd_slow = self._ema(closes[-120:], 26)
        macd = macd_fast - macd_slow
        macd_signal = self._ema([macd] * 9, 9)
        bb_mid = self._sma(closes, 20)
        atr14 = self._atr(highs, lows, closes, 14)
        # === 重构：让底层只提供指标，不自作主张下结论 ===
        score_buy = 0
        score_sell = 0
        evidence = []
        
        # 多头证据
        if ema9 > ema21:
            score_buy += 1
            evidence.append("ema9_gt_ema21")
        if macd > macd_signal:
            score_buy += 1
            evidence.append("macd_gt_signal")
        if close_last > bb_mid:
            score_buy += 1
            evidence.append("price_gt_bb_mid")
            
        # 空头证据
        if ema9 < ema21:
            score_sell += 1
            evidence.append("ema9_lt_ema21")
        if macd < macd_signal:
            score_sell += 1
            evidence.append("macd_lt_signal")
        if close_last < bb_mid:
            score_sell += 1
            evidence.append("price_lt_bb_mid")

        # 让信号具备方向性，而不是硬编码死多头
        if score_buy >= 2 and score_buy > score_sell:
            action = "OPEN"
            side = "BUY"
            confidence = 0.60 + 0.10 * score_buy
            sl = ask - max(atr14 * 1.5, 1.0)
            tp = ask + max(atr14 * 2.0, 1.5)
        elif score_sell >= 2 and score_sell > score_buy:
            action = "OPEN"
            side = "SELL"
            confidence = 0.60 + 0.10 * score_sell
            # 修复止损止盈方向错误。
            # SELL 的止损（防守）必须在上方：bid + ATR。止盈（进攻）必须在下方：bid - ATR。
            sl = bid + max(atr14 * 1.5, 1.0)
            tp = bid - max(atr14 * 2.0, 1.5)
        else:
            action = "HOLD"
            side = "NONE"
            confidence = 0.45
            sl = None
            tp = None
            if not evidence:
                evidence = ["indicator_signal_weak"]

        plan: Dict[str, Any] = {
            "signal_id": f"ea_live_{self.symbol}_{int(datetime.now(timezone.utc).timestamp())}",
            "symbol": self.symbol,
            "timeframe": self.timeframe,
            "action": action,
            "side": side,
            "regime": "TREND" if action == "OPEN" else "UNCERTAIN",
            "confidence": round(float(confidence), 4),
            "risk_per_trade_pct": 0.6,
            "volume_lots": 0.1,
            "risk_flags": ["EA_LIVE_SOURCE_CONNECTED"],
            "evidence": evidence,
            "invalidations": [],
            "valid_until": _utc_now_iso(),
            "indicators": {
                "ema9": round(float(ema9), 6),
                "ema21": round(float(ema21), 6),
                "macd": round(float(macd), 6),
                "macd_signal": round(float(macd_signal), 6),
                "bb_mid": round(float(bb_mid), 6),
                "atr14": round(float(atr14), 6),
            },
        }
        if sl is not None and tp is not None:
            # 修复：入场价也必须区分多空！BUY 用 ask，SELL 用 bid。
            entry_price = round(float(ask if side == "BUY" else bid), 6)
            
            # 【最后一道防线：倒挂免疫层】
            # 无论前面怎么算，只要在写入 JSON 前发现倒挂，立刻原地拨正！
            sl_val = round(float(sl), 6)
            tp_val = round(float(tp), 6)
            atr_v = round(float(atr14), 6)
            
            if side == "BUY":
                if sl_val >= entry_price:
                    sl_val = round(entry_price - 1.5 * atr_v, 6)
                if tp_val <= entry_price:
                    tp_val = round(entry_price + 2.0 * atr_v, 6)
            elif side == "SELL":
                if sl_val <= entry_price:
                    sl_val = round(entry_price + 1.5 * atr_v, 6)
                if tp_val >= entry_price:
                    tp_val = round(entry_price - 2.0 * atr_v, 6)

            plan["entry_price"] = entry_price
            plan["sl_price"] = sl_val
            plan["tp_price"] = tp_val
        account = payload.get("account", {})
        return {
            "market": {
                "market_data_delay_ms": float(market_data_delay_ms),
                "missing_bars": 0,
                "tick_bar_deviation_bps": round(float(tick_bar_deviation_bps), 4),
                "spread_points": float(tick.get("spread_points", 0)),
                "spread_p90_30m": self._percentile(spreads[-120:], 0.9) if spreads else float(tick.get("spread_points", 0)),
                "server_time": _utc_now_iso(),
                "bid": bid,
                "ask": ask,
            },
            "account": {
                "daily_realized_loss_pct": 0,
                "consecutive_losses": 0,
                "equity": float(account.get("equity", 0)),
            },
            "agent": {
                "agent_timeout_ms": 0,
                "risk_level": "MEDIUM",
                "side_conflict_with_regime": False,
            },
            "runtime_feedback": {
                "ack_timeout_count_5m": 0,
                "slippage_points_p95_15m": 0,
                "daily_token_usage_pct": 0,
            },
            "ai_trade_plan": plan,
        }
