"""
MT5 broker adapter — wraps existing mt5_execution.py behind the AbstractBroker interface.
"""
from __future__ import annotations

import json
import os
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from .interface import (
    AbstractBroker,
    AccountInfo,
    OrderRequest,
    OrderResult,
    OrderSide,
    OrderStatus,
    OrderType,
    Position,
    TickData,
    register_broker,
)

ROOT = Path(__file__).resolve().parent.parent.parent


def _resolve_position_side(raw_type: Any) -> OrderSide:
    """Safely resolve MT5 position type to OrderSide.

    MT5 convention: 0 = POSITION_TYPE_BUY, 1 = POSITION_TYPE_SELL.
    EA bridge may report "BUY" / "SELL" strings, or None/null.
    """
    if raw_type is None:
        return OrderSide.SELL
    if isinstance(raw_type, (int, float)):
        return OrderSide.BUY if int(raw_type) == 0 else OrderSide.SELL
    text = str(raw_type).strip().upper()
    if text in ("BUY", "0"):
        return OrderSide.BUY
    return OrderSide.SELL


class MT5BrokerAdapter(AbstractBroker):
    """
    MT5 broker adapter — delegates to existing mt5_execution.py.

    Uses the existing file-based bridge (to_ea/from_ea) for execution.
    Supports both 'direct' (Python-MT5 API) and 'ea_bridge' (EA file bridge) modes.
    """

    broker_id = "mt5"

    def __init__(self, mode: str = "ea_bridge", **kwargs):
        self._mode = mode  # 'direct' or 'ea_bridge'
        self._connected = False
        self._account_info: AccountInfo | None = None

    def connect(self) -> bool:
        if self._mode == "direct":
            return self._connect_direct()
        return self._connect_ea_bridge()

    def disconnect(self) -> None:
        self._connected = False

    def is_connected(self) -> bool:
        if self._mode == "direct":
            return self._check_direct()
        return self._check_ea_bridge()

    def send_order(self, request: OrderRequest) -> OrderResult:
        # Pre-trade compliance check
        block = self.pre_trade_check(request)
        if block:
            return block

        if self._mode == "direct":
            return self._send_direct(request)
        return self._send_ea_bridge(request)

    def cancel_order(self, order_id: str) -> OrderResult:
        if self._mode == "direct":
            return self._cancel_direct(order_id)
        return self._cancel_ea_bridge(order_id)

    def get_positions(self, symbol: str = "") -> list[Position]:
        return self._get_positions_via_ea(symbol)

    def get_account_info(self) -> AccountInfo:
        return self._get_account_via_ea()

    def get_tick(self, symbol: str) -> TickData | None:
        return self._get_tick_via_ea(symbol)

    # ─── Direct mode (Python-MT5 API) ──────────────────────────────

    def _connect_direct(self) -> bool:
        try:
            from src.mt5_execution import mt5_connect
            self._connected = mt5_connect()
            return self._connected
        except Exception:
            return False

    def _check_direct(self) -> bool:
        return self._connected

    def _send_direct(self, request: OrderRequest) -> OrderResult:
        try:
            from src.mt5_execution import mt5_send_order
            result = mt5_send_order(
                symbol=request.symbol,
                action=request.side.value,
                volume=request.volume,
                sl_price=request.sl_price,
                tp_price=request.tp_price,
                signal_id=request.signal_id,
            )
            return OrderResult(
                ok=result.get("ok", False),
                order_id=result.get("order_id", ""),
                status=OrderStatus.FILLED if result.get("ok") else OrderStatus.REJECTED,
                broker_raw=result,
            )
        except Exception as e:
            return OrderResult(ok=False, error_message=str(e), status=OrderStatus.ERROR)

    def _cancel_direct(self, order_id: str) -> OrderResult:
        try:
            from src.mt5_execution import mt5_cancel_order
            result = mt5_cancel_order(order_id)
            return OrderResult(ok=result.get("ok", False), order_id=order_id, broker_raw=result)
        except Exception as e:
            return OrderResult(ok=False, error_message=str(e), status=OrderStatus.ERROR)

    # ─── EA Bridge mode (file-based) ───────────────────────────────

    def _connect_ea_bridge(self) -> bool:
        to_ea, from_ea = self._bridge_paths()
        # Check that bridge directories exist (created by EA on MT5 side)
        self._connected = to_ea.exists() and from_ea.exists()
        return self._connected

    def _check_ea_bridge(self) -> bool:
        to_ea, from_ea = self._bridge_paths()
        return to_ea.exists() and from_ea.exists()

    def _send_ea_bridge(self, request: OrderRequest) -> OrderResult:
        to_ea, from_ea = self._bridge_paths()
        if not to_ea.exists():
            return OrderResult(ok=False, error_message="Bridge to_ea directory not found", status=OrderStatus.ERROR)

        signal = {
            "signal_id": request.signal_id or f"sig_{int(time.time()*1000)}",
            "action": request.side.value,
            "symbol": request.symbol,
            "volume": request.volume,
            "sl_price": request.sl_price,
            "tp_price": request.tp_price,
            "comment": request.comment,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }

        # Write signal file (EA polls this directory)
        signal_path = to_ea / f"{signal['signal_id']}.json"
        signal_path.parent.mkdir(parents=True, exist_ok=True)
        signal_path.write_text(json.dumps(signal, ensure_ascii=False), encoding="utf-8")

        return OrderResult(
            ok=True,
            order_id=signal["signal_id"],
            status=OrderStatus.SENT,
            broker_raw={"mode": "ea_bridge", "signal_path": str(signal_path)},
        )

    def _cancel_ea_bridge(self, order_id: str) -> OrderResult:
        to_ea, _ = self._bridge_paths()
        cancel = {"action": "CANCEL", "order_id": order_id}
        path = to_ea / f"cancel_{order_id}.json"
        path.write_text(json.dumps(cancel, ensure_ascii=False), encoding="utf-8")
        return OrderResult(ok=True, order_id=order_id, status=OrderStatus.CANCELLED)

    def _bridge_paths(self) -> tuple[Path, Path]:
        """Resolve EA bridge directories."""
        try:
            from src.ea_bridge_paths import resolve_bridge_layout
            layout = resolve_bridge_layout(ROOT)
            return layout.get("to_ea", Path(".")), layout.get("from_ea", Path("."))
        except ImportError:
            # Fallback to default MT5 common files path
            import os as _os4
            if sys.platform == "win32":
                base = Path(_os4.environ["APPDATA"]) / "MetaQuotes" / "Terminal" / "Common" / "Files" / "gold_autotrade" / "bridge"
            else:
                base = Path.home() / "Library/Application Support/net.metaquotes.wine.metatrader5" \
                       / "drive_c/users/user/AppData/Roaming/MetaQuotes/Terminal/Common/Files/gold_autotrade/bridge"
            return base / "to_ea", base / "from_ea"

    def _get_positions_via_ea(self, symbol: str = "") -> list[Position]:
        """Read positions from EA status file."""
        _, from_ea = self._bridge_paths()
        status_path = from_ea.parent / "terminal_status.json"
        if not status_path.exists():
            return []
        try:
            data = json.loads(status_path.read_text(encoding="utf-8"))
            positions = data.get("positions", [])
            result = []
            for p in positions:
                if symbol and p.get("symbol") != symbol:
                    continue
                result.append(Position(
                    ticket=str(p.get("ticket", "")),
                    symbol=p.get("symbol", ""),
                    side=_resolve_position_side(p.get("type")),
                    volume=float(p.get("volume", 0)),
                    open_price=float(p.get("open_price", 0)),
                    current_price=float(p.get("current_price", 0)),
                    sl=float(p.get("sl", 0)),
                    tp=float(p.get("tp", 0)),
                    profit=float(p.get("profit", 0)),
                    swap=float(p.get("swap", 0)),
                    commission=float(p.get("commission", 0)),
                    open_time=str(p.get("open_time", "")),
                ))
            return result
        except Exception:
            return []

    def _get_account_direct(self) -> AccountInfo:
        """直连MT5 API获取账户信息"""
        try:
            import MetaTrader5 as mt5
            if mt5.initialize():
                acct = mt5.account_info()
                if acct:
                    return AccountInfo(
                        broker="mt5",
                        account_id=str(getattr(acct, "login", "unknown")),
                        balance=float(getattr(acct, "balance", 0)),
                        equity=float(getattr(acct, "equity", 0)),
                        margin=float(getattr(acct, "margin", 0)),
                        margin_free=float(getattr(acct, "margin_free", 0)),
                        margin_level=float(getattr(acct, "margin_level", 0)),
                        currency=str(getattr(acct, "currency", "USD")),
                        leverage=int(getattr(acct, "leverage", 100)),
                        connected=True,
                    )
        except Exception:
            pass
        return AccountInfo(broker="mt5", account_id="unknown")

    def _get_account_via_ea(self) -> AccountInfo:
        _, from_ea = self._bridge_paths()
        status_path = from_ea.parent / "terminal_status.json"
        if not status_path.exists():
            return AccountInfo(broker="mt5", account_id="unknown")
        try:
            data = json.loads(status_path.read_text(encoding="utf-8"))
            acc = data.get("account", {})
            # ── account可能是字符串(login号)而非dict → MT5直连fallback ──
            if isinstance(acc, dict):
                balance = float(acc.get("balance", 0) or 0)
                equity = float(acc.get("equity", 0) or 0)
                if balance <= 0 or equity <= 0:
                    return self._get_account_direct()
                return AccountInfo(
                    broker="mt5",
                    account_id=str(acc.get("login", "")),
                    balance=balance,
                    equity=equity,
                    margin=float(acc.get("margin", 0)),
                    margin_free=float(acc.get("margin_free", 0)),
                    margin_level=float(acc.get("margin_level", 0)),
                    currency=acc.get("currency", "USD"),
                    leverage=int(acc.get("leverage", 100)),
                    connected=self._connected,
                )
            else:
                return self._get_account_direct()
        except Exception:
            return AccountInfo(broker="mt5", account_id="unknown")

    def _get_tick_via_ea(self, symbol: str) -> TickData | None:
        _, from_ea = self._bridge_paths()
        snapshot_path = from_ea.parent.parent / "market_snapshot.json"
        if not snapshot_path.exists():
            return None
        try:
            data = json.loads(snapshot_path.read_text(encoding="utf-8"))
            quotes = data.get("quotes", {})
            q = quotes.get(symbol, {})
            return TickData(
                symbol=symbol,
                bid=q.get("bid", 0),
                ask=q.get("ask", 0),
                spread=q.get("spread", 0),
            )
        except Exception:
            return None


# Auto-register
register_broker("mt5", MT5BrokerAdapter)
