"""
MT4 Bridge Client - Comunicación con EA Bridge en terminales MT4
OrbiQuant IA Trading Platform

Este cliente se comunica con el Expert Advisor (EA) corriendo en cada terminal MT4.
Cada terminal expone una API REST local que este cliente consume.
"""

import aiohttp
from datetime import datetime
from typing import Optional, Dict, List, Any
from dataclasses import dataclass, field
from enum import Enum
from loguru import logger


class OrderAction(str, Enum):
    """Acciones de trading"""
    BUY = "buy"
    SELL = "sell"
    CLOSE = "close"
    MODIFY = "modify"


@dataclass
class MT4Tick:
    """Tick data from MT4"""
    symbol: str
    bid: float
    ask: float
    timestamp: datetime
    spread: float = field(init=False)

    def __post_init__(self):
        self.spread = round(self.ask - self.bid, 5)


@dataclass
class MT4Position:
    """Open position in MT4"""
    ticket: int
    symbol: str
    type: str  # "buy" or "sell"
    lots: float
    open_price: float
    current_price: float
    stop_loss: Optional[float]
    take_profit: Optional[float]
    profit: float
    swap: float
    open_time: datetime
    magic: int
    comment: str


@dataclass
class MT4AccountInfo:
    """Account information from MT4"""
    balance: float
    equity: float
    margin: float
    free_margin: float
    margin_level: Optional[float]
    profit: float
    currency: str
    leverage: int
    name: str
    server: str
    company: str


@dataclass
class TradeResult:
    """Result of a trade operation"""
    success: bool
    ticket: Optional[int] = None
    message: str = ""
    error_code: Optional[int] = None


class MT4BridgeClient:
    """
    Cliente para comunicarse con un terminal MT4 via EA Bridge.

    El EA Bridge expone una API REST en un puerto local que permite:
    - Obtener información de cuenta
    - Obtener precios en tiempo real
    - Ejecutar operaciones de trading
    - Gestionar posiciones

    Uso:
        client = MT4BridgeClient(host="localhost", port=8081)
        account = await client.get_account_info()
        tick = await client.get_tick("XAUUSD")
        result = await client.open_trade("XAUUSD", "buy", 0.01, sl=2640, tp=2660)
    """

    def __init__(
        self,
        host: str = "localhost",
        port: int = 8081,
        auth_token: str = "secret",
        timeout: int = 10
    ):
        """
        Args:
            host: Host donde corre el terminal MT4
            port: Puerto del EA Bridge
            auth_token: Token de autenticación
            timeout: Timeout para requests en segundos
        """
        self.host = host
        self.port = port
        self.base_url = f"http://{host}:{port}"
        self.auth_token = auth_token
        self.timeout = aiohttp.ClientTimeout(total=timeout)
        self._session: Optional[aiohttp.ClientSession] = None

    async def _get_session(self) -> aiohttp.ClientSession:
        """Obtiene o crea sesión HTTP"""
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession(
                timeout=self.timeout,
                headers={
                    "Authorization": f"Bearer {self.auth_token}",
                    "Content-Type": "application/json"
                }
            )
        return self._session

    async def close(self):
        """Cierra la sesión HTTP"""
        if self._session:
            await self._session.close()
            self._session = None

    async def _request(
        self,
        method: str,
        endpoint: str,
        json_data: Optional[Dict] = None,
        params: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """Realiza request HTTP al EA Bridge"""
        session = await self._get_session()
        url = f"{self.base_url}{endpoint}"

        try:
            async with session.request(
                method,
                url,
                json=json_data,
                params=params
            ) as response:
                if response.status == 200:
                    return await response.json()
                else:
                    error_text = await response.text()
                    logger.error(f"MT4 Bridge error: {response.status} - {error_text}")
                    raise Exception(f"MT4 Bridge error: {error_text}")

        except aiohttp.ClientError as e:
            logger.error(f"Connection error to MT4 Bridge: {e}")
            raise

    # ==========================================
    # Account Information
    # ==========================================

    async def get_account_info(self) -> MT4AccountInfo:
        """Obtiene información de la cuenta MT4"""
        data = await self._request("GET", "/account")

        return MT4AccountInfo(
            balance=data.get("balance", 0),
            equity=data.get("equity", 0),
            margin=data.get("margin", 0),
            free_margin=data.get("freeMargin", 0),
            margin_level=data.get("marginLevel"),
            profit=data.get("profit", 0),
            currency=data.get("currency", "USD"),
            leverage=data.get("leverage", 100),
            name=data.get("name", ""),
            server=data.get("server", ""),
            company=data.get("company", "")
        )

    async def is_connected(self) -> bool:
        """Verifica si el terminal está conectado"""
        try:
            data = await self._request("GET", "/status")
            return data.get("connected", False)
        except Exception:
            return False

    # ==========================================
    # Market Data
    # ==========================================

    async def get_tick(self, symbol: str) -> MT4Tick:
        """Obtiene precio actual de un símbolo"""
        data = await self._request("GET", f"/tick/{symbol}")

        return MT4Tick(
            symbol=symbol,
            bid=data.get("bid", 0),
            ask=data.get("ask", 0),
            timestamp=datetime.fromisoformat(data.get("time", datetime.now().isoformat()))
        )

    async def get_ticks(self, symbols: List[str]) -> Dict[str, MT4Tick]:
        """Obtiene precios de múltiples símbolos"""
        data = await self._request("GET", "/ticks", params={"symbols": ",".join(symbols)})

        result = {}
        for symbol, tick_data in data.items():
            result[symbol] = MT4Tick(
                symbol=symbol,
                bid=tick_data.get("bid", 0),
                ask=tick_data.get("ask", 0),
                timestamp=datetime.fromisoformat(tick_data.get("time", datetime.now().isoformat()))
            )

        return result

    # ==========================================
    # Position Management
    # ==========================================

    async def get_positions(self) -> List[MT4Position]:
        """Obtiene todas las posiciones abiertas"""
        data = await self._request("GET", "/positions")

        positions = []
        for p in data:
            positions.append(MT4Position(
                ticket=p.get("ticket", 0),
                symbol=p.get("symbol", ""),
                type=p.get("type", ""),
                lots=p.get("lots", 0),
                open_price=p.get("openPrice", 0),
                current_price=p.get("currentPrice", 0),
                stop_loss=p.get("stopLoss"),
                take_profit=p.get("takeProfit"),
                profit=p.get("profit", 0),
                swap=p.get("swap", 0),
                open_time=datetime.fromisoformat(p.get("openTime", datetime.now().isoformat())),
                magic=p.get("magic", 0),
                comment=p.get("comment", "")
            ))

        return positions

    async def get_position(self, ticket: int) -> Optional[MT4Position]:
        """Obtiene una posición específica por ticket"""
        positions = await self.get_positions()
        for p in positions:
            if p.ticket == ticket:
                return p
        return None

    # ==========================================
    # Trading Operations
    # ==========================================

    async def open_trade(
        self,
        symbol: str,
        action: str,
        lots: float,
        sl: Optional[float] = None,
        tp: Optional[float] = None,
        price: Optional[float] = None,
        slippage: int = 3,
        magic: int = 12345,
        comment: str = "OrbiQuant"
    ) -> TradeResult:
        """
        Abre una nueva operación.

        Args:
            symbol: Símbolo a operar (ej: "XAUUSD")
            action: "buy" o "sell"
            lots: Volumen en lotes
            sl: Stop Loss (precio)
            tp: Take Profit (precio)
            price: Precio para órdenes pendientes (None para mercado)
            slippage: Slippage máximo en puntos
            magic: Magic number para identificación
            comment: Comentario de la orden

        Returns:
            TradeResult con el resultado de la operación
        """
        payload = {
            "action": action,
            "symbol": symbol,
            "lots": lots,
            "slippage": slippage,
            "magic": magic,
            "comment": comment
        }

        if sl is not None:
            payload["stopLoss"] = sl
        if tp is not None:
            payload["takeProfit"] = tp
        if price is not None:
            payload["price"] = price

        try:
            data = await self._request("POST", "/trade", json_data=payload)

            return TradeResult(
                success=data.get("success", False),
                ticket=data.get("ticket"),
                message=data.get("message", ""),
                error_code=data.get("errorCode")
            )

        except Exception as e:
            return TradeResult(
                success=False,
                message=str(e)
            )

    async def close_position(
        self,
        ticket: int,
        lots: Optional[float] = None,
        slippage: int = 3
    ) -> TradeResult:
        """
        Cierra una posición.

        Args:
            ticket: Ticket de la posición a cerrar
            lots: Volumen a cerrar (None = todo)
            slippage: Slippage máximo

        Returns:
            TradeResult
        """
        payload = {
            "action": "close",
            "ticket": ticket,
            "slippage": slippage
        }

        if lots is not None:
            payload["lots"] = lots

        try:
            data = await self._request("POST", "/trade", json_data=payload)

            return TradeResult(
                success=data.get("success", False),
                ticket=ticket,
                message=data.get("message", ""),
                error_code=data.get("errorCode")
            )

        except Exception as e:
            return TradeResult(
                success=False,
                message=str(e)
            )

    async def modify_position(
        self,
        ticket: int,
        sl: Optional[float] = None,
        tp: Optional[float] = None
    ) -> TradeResult:
        """
        Modifica SL/TP de una posición.

        Args:
            ticket: Ticket de la posición
            sl: Nuevo Stop Loss (None = sin cambio)
            tp: Nuevo Take Profit (None = sin cambio)

        Returns:
            TradeResult
        """
        payload = {
            "action": "modify",
            "ticket": ticket
        }

        if sl is not None:
            payload["stopLoss"] = sl
        if tp is not None:
            payload["takeProfit"] = tp

        try:
            data = await self._request("POST", "/trade", json_data=payload)

            return TradeResult(
                success=data.get("success", False),
                ticket=ticket,
                message=data.get("message", ""),
                error_code=data.get("errorCode")
            )

        except Exception as e:
            return TradeResult(
                success=False,
                message=str(e)
            )

    async def close_all_positions(self, symbol: Optional[str] = None) -> List[TradeResult]:
        """
        Cierra todas las posiciones (opcionalmente filtradas por símbolo).

        Args:
            symbol: Filtrar por símbolo (None = todas)

        Returns:
            Lista de TradeResult
        """
        positions = await self.get_positions()
        results = []

        for position in positions:
            if symbol is None or position.symbol == symbol:
                result = await self.close_position(position.ticket)
                results.append(result)

        return results

    # ==========================================
    # History
    # ==========================================

    async def get_history(
        self,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        symbol: Optional[str] = None
    ) -> List[Dict]:
        """Obtiene historial de operaciones cerradas"""
        params = {}
        if start_date:
            params["start"] = start_date.isoformat()
        if end_date:
            params["end"] = end_date.isoformat()
        if symbol:
            params["symbol"] = symbol

        data = await self._request("GET", "/history", params=params)
        return data

    # ==========================================
    # Symbol Information
    # ==========================================

    async def get_symbol_info(self, symbol: str) -> Dict:
        """Obtiene especificación del símbolo"""
        data = await self._request("GET", f"/symbol/{symbol}")
        return data

    async def get_symbols(self) -> List[str]:
        """Lista símbolos disponibles"""
        data = await self._request("GET", "/symbols")
        return data


# Context manager support
class MT4BridgeClientContext:
    """Context manager para MT4BridgeClient"""

    def __init__(self, host: str, port: int, auth_token: str = "secret"):
        self.client = MT4BridgeClient(host, port, auth_token)

    async def __aenter__(self) -> MT4BridgeClient:
        return self.client

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.client.close()


# Convenience function
async def create_mt4_client(
    host: str = "localhost",
    port: int = 8081,
    auth_token: str = "secret"
) -> MT4BridgeClient:
    """Crea y verifica conexión con MT4 Bridge"""
    client = MT4BridgeClient(host, port, auth_token)

    if await client.is_connected():
        logger.info(f"Connected to MT4 Bridge at {host}:{port}")
        return client
    else:
        raise Exception(f"MT4 Bridge at {host}:{port} is not responding")
