"""
MT5 to Laravel Sync Bridge
Connects to MetaTrader 5, fetches trading data, and syncs to Laravel API
"""

import MetaTrader5 as mt5
import requests
import time
import logging
from datetime import datetime, timedelta
from typing import Dict, List

# Configuration
LARAVEL_API_URL = "http://127.0.0.1:8000/api/v1/mt5/sync"
API_KEY = "mt5-bridge"
MT5_LOGIN = 198164019
MT5_PASSWORD = "ECMliveTR4D3$"
MT5_SERVER = "HFMarketsGlobal-Live16"
SYNC_INTERVAL_SECONDS = 120  # Sync every 2 minutes

# Logging setup
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('mt5_sync.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class MT5SyncBridge:
    def __init__(self):
        self.connected = False
        self.last_sync_time = None

    def connect(self) -> bool:
        """Connect to MT5 terminal"""
        try:
            # Shutdown any existing MT5 connections
            mt5.shutdown()

            # Initialize exact terminal instance with credentials
            # This attaches to the already open terminal instance
            initialized = mt5.initialize(
                path=r"C:\Program Files\MetaTrader 5\terminal64.exe",
                login=MT5_LOGIN,
                password=MT5_PASSWORD,
                server=MT5_SERVER,
                timeout=60000,
                portable=False
            )

            if not initialized:
                logger.error(f"MT5 initialize failed: {mt5.last_error()}")
                return False

            # Verify connected account
            account_info = mt5.account_info()
            logger.info(f"Connected account: {account_info.login}")

            # Verify terminal info and log data paths
            terminal_info = mt5.terminal_info()
            logger.info(f"MT5 Terminal: {terminal_info}")
            logger.info(f"MT5 data path: {terminal_info.data_path}")
            logger.info(f"MT5 common path: {terminal_info.commondata_path}")

            # Log additional diagnostics
            logger.info(f"Account Info: {mt5.account_info()}")
            logger.info(f"Version: {mt5.version()}")

            self.connected = True
            logger.info(f"Connected to MT5 account: {MT5_LOGIN}")
            return True

        except Exception as e:
            logger.error(f"Connection error: {str(e)}")
            return False

    def disconnect(self):
        """Disconnect from MT5 terminal"""
        if self.connected:
            mt5.shutdown()
            self.connected = False
            logger.info("Disconnected from MT5")

    def get_account_info(self) -> Dict:
        """Fetch account information"""
        try:
            account_info = mt5.account_info()
            if account_info is None:
                logger.error("Failed to get account info")
                return None

            return {
                "account_number": str(account_info.login),
                "account_name": account_info.name,
                "server": account_info.server,
                "balance": float(account_info.balance),
                "equity": float(account_info.equity),
                "margin": float(account_info.margin),
                "free_margin": float(account_info.margin_free),
                "floating_profit": float(account_info.equity - account_info.balance),
                "currency": account_info.currency,
                "leverage": account_info.leverage
            }
        except Exception as e:
            logger.error(f"Error getting account info: {str(e)}")
            return None

    def get_open_trades(self) -> List[Dict]:
        """Fetch all open trades"""
        try:
            positions = mt5.positions_get()

            if positions is None:
                logger.error("Failed to get open trades")
                return []

            trades = []

            for position in positions:
                trades.append({
                    "ticket": str(position.ticket),
                    "symbol": position.symbol,
                    "type": "BUY" if position.type == 0 else "SELL",
                    "volume": float(position.volume),
                    "open_price": float(position.price_open),
                    "current_price": float(position.price_current),
                    "sl": float(position.sl) if position.sl > 0 else None,
                    "tp": float(position.tp) if position.tp > 0 else None,
                    "profit": float(position.profit),
                    "swap": float(getattr(position, "swap", 0)),
                    "commission": float(getattr(position, "commission", 0)),
                    "open_time": datetime.fromtimestamp(position.time).isoformat(),
                    "status": "OPEN",
                    "comment": position.comment if position.comment else ""
                })

            logger.info(f"Fetched {len(trades)} open trades")
            return trades

        except Exception as e:
            logger.error(f"Error getting open trades: {str(e)}")
            return []

    def get_closed_trades(self, from_date: datetime, to_date: datetime) -> List[Dict]:
        """Fetch closed trades within date range using position-based reconstruction"""
        try:
            # Log requested range for debugging
            logger.info(f"Fetching MT5 history from {from_date} to {to_date}")

            # Fetch deal history
            history_deals = mt5.history_deals_get(from_date, to_date)
            if history_deals is None:
                error = mt5.last_error()
                logger.error(f"Failed to get closed trades: {error}")
                logger.info(f"MT5 last error: {mt5.last_error()}")
                return []

            logger.info(f"Raw MT5 history deals count: {len(history_deals)}")

            # Group deals by position_id for reconstruction
            positions = {}

            for deal in history_deals:
                # Log raw deal for debugging
                logger.info(f"RAW MT5 DEAL OBJECT: {deal}")

                position_id = deal.position_id
                entry = deal.entry
                symbol = deal.symbol
                volume = deal.volume

                # Skip invalid deals
                if not symbol or volume <= 0:
                    logger.warning(
                        f"SKIPPING INVALID DEAL | "
                        f"ticket={deal.ticket} | "
                        f"symbol={symbol} | "
                        f"volume={volume}"
                    )
                    continue

                # Initialize position entry if not exists
                if position_id not in positions:
                    positions[position_id] = {
                        "open": None,
                        "closes": [],
                        "deals": [],
                        "symbol": symbol
                    }

                # Track all deals for debugging
                positions[position_id]["deals"].append(deal)

                # Store ENTRY_IN deal as open
                if entry == mt5.DEAL_ENTRY_IN:
                    positions[position_id]["open"] = deal
                    logger.info(
                        f"ENTRY_IN DEAL | "
                        f"position_id={position_id} | "
                        f"ticket={deal.ticket} | "
                        f"symbol={symbol}"
                    )

                # Store all close deal types (OUT, OUT_BY, INOUT)
                elif entry in [
                    mt5.DEAL_ENTRY_OUT,
                    mt5.DEAL_ENTRY_OUT_BY,
                    mt5.DEAL_ENTRY_INOUT,
                ]:
                    positions[position_id]["closes"].append(deal)
                    entry_name = {
                        mt5.DEAL_ENTRY_OUT: "ENTRY_OUT",
                        mt5.DEAL_ENTRY_OUT_BY: "ENTRY_OUT_BY",
                        mt5.DEAL_ENTRY_INOUT: "ENTRY_INOUT",
                    }.get(entry, f"ENTRY_{entry}")
                    logger.info(
                        f"{entry_name} DEAL | "
                        f"position_id={position_id} | "
                        f"ticket={deal.ticket} | "
                        f"symbol={symbol} | "
                        f"profit={deal.profit}"
                    )

            # Reconstruct closed trades from position data
            trades = []

            for position_id, position_data in positions.items():
                open_deal = position_data["open"]
                close_deals = position_data["closes"]
                all_deals = position_data["deals"]

                # Only include positions with at least one open and one close deal
                if open_deal is None or not close_deals:
                    logger.warning(
                        f"SKIPPING POSITION | "
                        f"position_id={position_id} | "
                        f"open_deals={1 if open_deal else 0} | "
                        f"close_deals={len(close_deals)} | "
                        f"entries={[d.entry for d in all_deals]}"
                    )
                    continue

                # Aggregate data from all close deals
                total_profit = sum(deal.profit for deal in close_deals)
                total_swap = sum(getattr(deal, "swap", 0) for deal in close_deals)
                total_commission = sum(getattr(deal, "commission", 0) for deal in close_deals)

                # Use the latest close deal for price and time
                latest_close = max(close_deals, key=lambda d: d.time)

                symbol = latest_close.symbol
                volume = open_deal.volume  # Use open volume for position size

                # Map MT5 type to BUY/SELL based on open deal type
                if open_deal.type == 0:
                    trade_type = "BUY"
                elif open_deal.type == 1:
                    trade_type = "SELL"
                else:
                    logger.warning(
                        f"SKIPPING UNKNOWN TRADE TYPE | "
                        f"position_id={position_id} | "
                        f"type={open_deal.type}"
                    )
                    continue

                # Debug logging for reconstructed closed trade
                logger.info(
                    f"RECONSTRUCTED CLOSED TRADE | "
                    f"position_id={position_id} | "
                    f"symbol={symbol} | "
                    f"type={trade_type} | "
                    f"profit={total_profit}"
                )

                # Build final closed trade payload with aggregated close data
                trades.append({
                    "ticket": str(position_id),
                    "symbol": symbol,
                    "type": trade_type,
                    "volume": float(volume),
                    "open_price": float(open_deal.price),
                    "current_price": float(latest_close.price),
                    "profit": float(total_profit),
                    "swap": float(total_swap),
                    "commission": float(total_commission),
                    "open_time": datetime.fromtimestamp(open_deal.time).isoformat(),
                    "close_time": datetime.fromtimestamp(latest_close.time).isoformat(),
                    "status": "CLOSED",
                    "comment": latest_close.comment if latest_close.comment else ""
                })

                logger.info(
                    f"CLOSED TRADE FINAL | "
                    f"ticket={position_id} | "
                    f"type={trade_type} | "
                    f"symbol={symbol} | "
                    f"open_price={open_deal.price} | "
                    f"close_price={latest_close.price} | "
                    f"profit={total_profit}"
                )

            logger.info(f"Reconstructed closed trades count: {len(trades)}")
            return trades

        except Exception as e:
            logger.error(f"Error getting closed trades: {str(e)}")
            return []

    def sync_to_laravel(self) -> bool:
        """Sync MT5 data to Laravel API"""
        try:
            # Get account info
            account_info = self.get_account_info()
            if not account_info:
                return False

            # Get open trades
            open_trades = self.get_open_trades()
            # Get closed trades (starting from year 2026)
            to_date = datetime.now()
            from_date = datetime(2026, 1, 1)

            closed_trades = self.get_closed_trades(from_date, to_date)

            logger.info(f"Open trades: {len(open_trades)}")
            logger.info(f"Closed trades: {len(closed_trades)}")

            # Prepare sync payload
            payload = {
                "api_key": API_KEY,
                "timestamp": int(time.time()),
                "account": account_info,
                "open_trades": open_trades,
                "closed_trades": closed_trades
            }

            # Send to Laravel API
            response = requests.post(
                LARAVEL_API_URL,
                json=payload,
                headers={"Content-Type": "application/json"},
                timeout=30
            )

            if response.status_code == 202:
                logger.info("MT5 sync queued successfully in Laravel")
                self.last_sync_time = datetime.now()
                return True
            else:
                logger.error(f"Sync failed: {response.status_code} - {response.text}")
                return False

        except requests.exceptions.Timeout:
            logger.error("Sync request timed out")
            return False
        except requests.exceptions.RequestException as e:
            logger.error(f"Sync request error: {str(e)}")
            return False
        except Exception as e:
            logger.error(f"Sync error: {str(e)}")
            return False

    def run_sync_loop(self):
        """Run continuous sync loop"""
        logger.info("Starting MT5 sync loop")

        if not self.connect():
            logger.error("Failed to connect to MT5. Exiting.")
            return

        try:
            while True:
                try:
                    logger.info("Starting sync cycle")
                    success = self.sync_to_laravel()

                    if success:
                        logger.info(f"Sync completed. Next sync in {SYNC_INTERVAL_SECONDS}s")
                    else:
                        logger.warning("Sync failed. Retrying...")

                    time.sleep(SYNC_INTERVAL_SECONDS)

                except KeyboardInterrupt:
                    logger.info("Received interrupt signal. Stopping...")
                    break
                except Exception as e:
                    logger.error(f"Error in sync loop: {str(e)}")
                    time.sleep(60)  # Wait 1 minute before retry

        finally:
            self.disconnect()


def main():
    """Main entry point"""
    bridge = MT5SyncBridge()
    bridge.run_sync_loop()


if __name__ == "__main__":
    main()
