"""
Multi-broker execution abstraction — unified interface for MT5, cTrader, TradingView, etc.

All execution code calls this interface instead of MT5 directly.
Adding a new broker = implementing the 7 methods below.

Currently implemented: MT5 (via existing mt5_execution.py)
Planned: cTrader, TradingView webhook, Interactive Brokers, DxTrade
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any


class OrderType(Enum):
    MARKET = "MARKET"
    LIMIT = "LIMIT"
    STOP = "STOP"


class OrderSide(Enum):
    BUY = "BUY"
    SELL = "SELL"


class OrderStatus(Enum):
    CREATED = "CREATED"
    SENT = "SENT"
    ACKED = "ACKED"
    FILLED = "FILLED"
    PARTIALLY_FILLED = "PARTIALLY_FILLED"
    REJECTED = "REJECTED"
    CANCELLED = "CANCELLED"
    TIMEOUT = "TIMEOUT"
    ERROR = "ERROR"


@dataclass
class OrderRequest:
    """Normalized order request — all brokers receive this same structure."""
    symbol: str
    side: OrderSide
    order_type: OrderType
    volume: float
    price: float = 0.0          # 0 = market price
    sl_price: float = 0.0
    tp_price: float = 0.0
    comment: str = ""
    magic_number: int = 0
    signal_id: str = ""
    request_id: str = ""


@dataclass
class OrderResult:
    """Normalized order result — all brokers return this same structure."""
    ok: bool
    order_id: str = ""
    broker_order_id: str = ""
    status: OrderStatus = OrderStatus.CREATED
    filled_price: float = 0.0
    filled_volume: float = 0.0
    commission: float = 0.0
    swap: float = 0.0
    error_code: str = ""
    error_message: str = ""
    broker_raw: dict[str, Any] = field(default_factory=dict)
    timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


@dataclass
class AccountInfo:
    """Normalized account information."""
    broker: str
    account_id: str
    balance: float = 0.0
    equity: float = 0.0
    margin: float = 0.0
    margin_free: float = 0.0
    margin_level: float = 0.0  # percent
    currency: str = "USD"
    leverage: int = 100
    connected: bool = False


@dataclass
class Position:
    """Normalized open position."""
    ticket: str
    symbol: str
    side: OrderSide
    volume: float
    open_price: float
    current_price: float
    sl: float
    tp: float
    profit: float
    swap: float
    commission: float
    open_time: str


@dataclass
class TickData:
    """Normalized tick/quote data."""
    symbol: str
    bid: float
    ask: float
    spread: float
    volume: int = 0
    timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ─── Abstract Broker Interface ─────────────────────────────────────

class AbstractBroker(ABC):
    """All broker adapters must implement these 7 methods."""

    broker_id: str = "abstract"

    @abstractmethod
    def connect(self) -> bool:
        """Initialize connection to the broker. Returns True on success."""
        ...

    @abstractmethod
    def disconnect(self) -> None:
        """Clean up connection."""
        ...

    @abstractmethod
    def is_connected(self) -> bool:
        """Check if connection is alive."""
        ...

    @abstractmethod
    def send_order(self, request: OrderRequest) -> OrderResult:
        """Send an order to the broker."""
        ...

    @abstractmethod
    def cancel_order(self, order_id: str) -> OrderResult:
        """Cancel a pending order."""
        ...

    @abstractmethod
    def get_positions(self, symbol: str = "") -> list[Position]:
        """Get open positions, optionally filtered by symbol."""
        ...

    @abstractmethod
    def get_account_info(self) -> AccountInfo:
        """Get account balance, equity, margin info."""
        ...

    @abstractmethod
    def get_tick(self, symbol: str) -> TickData | None:
        """Get current bid/ask for a symbol."""
        ...

    # Optional hooks
    def pre_trade_check(self, request: OrderRequest) -> OrderResult | None:
        """Return OrderResult to block the trade, or None to proceed."""
        return None

    def on_trade_completed(self, result: OrderResult) -> None:
        """Called after a trade completes (logging, notifications, etc.)."""
        pass


# ─── Broker Registry ───────────────────────────────────────────────

_registry: dict[str, type[AbstractBroker]] = {}


def register_broker(broker_id: str, broker_class: type[AbstractBroker]) -> None:
    """Register a broker implementation."""
    _registry[broker_id] = broker_class


def get_broker(broker_id: str, **kwargs) -> AbstractBroker:
    """Get a broker instance by ID."""
    if broker_id not in _registry:
        raise ValueError(f"Unknown broker: {broker_id}. Available: {list(_registry)}")
    return _registry[broker_id](**kwargs)


def list_brokers() -> list[str]:
    """List all registered broker IDs."""
    return sorted(_registry.keys())
