#!/usr/bin/env python3
"""
Live Trading Bot for Regime Reversion Strategy on OKX.

This script runs the regime-based mean reversion strategy
on ETH perpetual futures using OKX exchange.

Usage:
    # Run with demo account (default)
    uv run python -m live_trading.main
    
    # Run with specific settings
    uv run python -m live_trading.main --max-position 500 --leverage 2
"""
import argparse
import logging
import signal
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from live_trading.config import get_config, OKXConfig, TradingConfig, PathConfig
from live_trading.okx_client import OKXClient
from live_trading.data_feed import DataFeed
from live_trading.position_manager import PositionManager
from live_trading.live_regime_strategy import LiveRegimeStrategy


def setup_logging(log_dir: Path) -> logging.Logger:
    """Configure logging for the trading bot."""
    log_file = log_dir / "live_trading.log"
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
        handlers=[
            logging.FileHandler(log_file),
            logging.StreamHandler(sys.stdout),
        ],
        force=True
    )
    
    return logging.getLogger(__name__)


class LiveTradingBot:
    """
    Main trading bot orchestrator.
    
    Coordinates data fetching, signal generation, and order execution
    in a continuous loop.
    """
    
    def __init__(
        self,
        okx_config: OKXConfig,
        trading_config: TradingConfig,
        path_config: PathConfig
    ):
        self.okx_config = okx_config
        self.trading_config = trading_config
        self.path_config = path_config
        
        self.logger = logging.getLogger(__name__)
        self.running = True
        
        # Initialize components
        self.logger.info("Initializing trading bot components...")
        
        self.okx_client = OKXClient(okx_config, trading_config)
        self.data_feed = DataFeed(self.okx_client, trading_config, path_config)
        self.position_manager = PositionManager(
            self.okx_client, trading_config, path_config
        )
        self.strategy = LiveRegimeStrategy(trading_config, path_config)
        
        # Register signal handlers for graceful shutdown
        signal.signal(signal.SIGINT, self._handle_shutdown)
        signal.signal(signal.SIGTERM, self._handle_shutdown)
        
        self._print_startup_banner()
    
    def _print_startup_banner(self) -> None:
        """Print startup information."""
        mode = "DEMO/SANDBOX" if self.okx_config.demo_mode else "LIVE"
        
        print("=" * 60)
        print(f"  Regime Reversion Strategy - Live Trading Bot")
        print("=" * 60)
        print(f"  Mode:           {mode}")
        print(f"  Trading Pair:   {self.trading_config.eth_symbol}")
        print(f"  Context Pair:   {self.trading_config.btc_symbol}")
        print(f"  Timeframe:      {self.trading_config.timeframe}")
        print(f"  Max Position:   ${self.trading_config.max_position_usdt if self.trading_config.max_position_usdt > 0 else 'All available'}")
        print(f"  Leverage:       {self.trading_config.leverage}x")
        print(f"  Stop Loss:      {self.trading_config.stop_loss_pct * 100:.1f}%")
        print(f"  Take Profit:    {self.trading_config.take_profit_pct * 100:.1f}%")
        print(f"  Cycle Interval: {self.trading_config.sleep_seconds // 60} minutes")
        print("=" * 60)
        
        if not self.okx_config.demo_mode:
            print("\n  *** WARNING: LIVE TRADING MODE - REAL FUNDS AT RISK ***\n")
    
    def _handle_shutdown(self, signum, frame) -> None:
        """Handle shutdown signals gracefully."""
        self.logger.info("Shutdown signal received, stopping...")
        self.running = False
    
    def run_trading_cycle(self) -> None:
        """
        Execute one trading cycle.
        
        1. Fetch latest market data
        2. Update open positions
        3. Generate trading signal
        4. Execute trades if signal triggers
        """
        cycle_start = datetime.now(timezone.utc)
        self.logger.info(f"--- Trading Cycle Start: {cycle_start.isoformat()} ---")
        
        try:
            # 1. Fetch market data
            features = self.data_feed.get_latest_data()
            if features is None or features.empty:
                self.logger.warning("No market data available, skipping cycle")
                return
            
            # Get current prices
            eth_price = features['eth_close'].iloc[-1]
            btc_price = features['btc_close'].iloc[-1]
            
            current_prices = {
                self.trading_config.eth_symbol: eth_price,
                self.trading_config.btc_symbol: btc_price,
            }
            
            # 2. Update existing positions (check SL/TP)
            closed_trades = self.position_manager.update_positions(current_prices)
            if closed_trades:
                for trade in closed_trades:
                    self.logger.info(
                        f"Trade closed: {trade['trade_id']} "
                        f"PnL=${trade['pnl_usd']:.2f} ({trade['reason']})"
                    )
            
            # 3. Sync with exchange positions
            self.position_manager.sync_with_exchange()
            
            # 4. Get current funding rates
            funding = self.data_feed.get_current_funding_rates()
            
            # 5. Generate trading signal
            signal = self.strategy.generate_signal(features, funding)
            
            # 6. Execute trades based on signal
            if signal['action'] == 'entry':
                self._execute_entry(signal, eth_price)
            elif signal['action'] == 'check_exit':
                self._execute_exit(signal)
            
            # 7. Log portfolio summary
            summary = self.position_manager.get_portfolio_summary()
            self.logger.info(
                f"Portfolio: {summary['open_positions']} positions, "
                f"exposure=${summary['total_exposure_usdt']:.2f}, "
                f"unrealized_pnl=${summary['total_unrealized_pnl']:.2f}"
            )
            
        except Exception as e:
            self.logger.error(f"Trading cycle error: {e}", exc_info=True)
            # Save positions on error
            self.position_manager.save_positions()
        
        cycle_duration = (datetime.now(timezone.utc) - cycle_start).total_seconds()
        self.logger.info(f"--- Cycle completed in {cycle_duration:.1f}s ---")
    
    def _execute_entry(self, signal: dict, current_price: float) -> None:
        """Execute entry trade."""
        symbol = self.trading_config.eth_symbol
        side = signal['side']
        
        # Check if we can open a position
        if not self.position_manager.can_open_position():
            self.logger.info("Cannot open position: max positions reached")
            return
        
        # Get account balance
        balance = self.okx_client.get_balance()
        available_usdt = balance['free']
        
        # Calculate position size
        size_usdt = self.strategy.calculate_position_size(signal, available_usdt)
        if size_usdt <= 0:
            self.logger.info("Position size too small, skipping entry")
            return
        
        size_eth = size_usdt / current_price
        
        # Calculate SL/TP
        stop_loss, take_profit = self.strategy.calculate_sl_tp(current_price, side)
        
        self.logger.info(
            f"Executing {side.upper()} entry: {size_eth:.4f} ETH @ {current_price:.2f} "
            f"(${size_usdt:.2f}), SL={stop_loss:.2f}, TP={take_profit:.2f}"
        )
        
        try:
            # Place market order
            order_side = "buy" if side == "long" else "sell"
            order = self.okx_client.place_market_order(symbol, order_side, size_eth)
            
            # Get filled price (handle None values from OKX response)
            filled_price = order.get('average') or order.get('price') or current_price
            filled_amount = order.get('filled') or order.get('amount') or size_eth
            
            # Ensure we have valid numeric values
            if filled_price is None or filled_price == 0:
                self.logger.warning(f"No fill price in order response, using current price: {current_price}")
                filled_price = current_price
            if filled_amount is None or filled_amount == 0:
                self.logger.warning(f"No fill amount in order response, using requested: {size_eth}")
                filled_amount = size_eth
            
            # Recalculate SL/TP with filled price
            stop_loss, take_profit = self.strategy.calculate_sl_tp(filled_price, side)
            
            # Get order ID from response
            order_id = order.get('id', '')
            
            # Record position locally
            position = self.position_manager.open_position(
                symbol=symbol,
                side=side,
                entry_price=filled_price,
                size=filled_amount,
                stop_loss_price=stop_loss,
                take_profit_price=take_profit,
                order_id=order_id,
            )
            
            if position:
                self.logger.info(
                    f"Position opened: {position.trade_id}, "
                    f"{filled_amount:.4f} ETH @ {filled_price:.2f}"
                )
                
                # Try to set SL/TP on exchange
                try:
                    self.okx_client.set_stop_loss_take_profit(
                        symbol, side, filled_amount, stop_loss, take_profit
                    )
                except Exception as e:
                    self.logger.warning(f"Could not set SL/TP on exchange: {e}")
            
        except Exception as e:
            self.logger.error(f"Order execution failed: {e}", exc_info=True)
    
    def _execute_exit(self, signal: dict) -> None:
        """Execute exit based on mean reversion signal."""
        symbol = self.trading_config.eth_symbol
        
        # Get position for ETH
        position = self.position_manager.get_position_for_symbol(symbol)
        if not position:
            return
        
        current_price = signal.get('eth_price', position.current_price)
        
        self.logger.info(
            f"Mean reversion exit signal: closing {position.trade_id} "
            f"@ {current_price:.2f}"
        )
        
        try:
            # Close position on exchange
            exit_order = self.okx_client.close_position(symbol)
            exit_order_id = exit_order.get('id', '') if exit_order else ''
            
            # Record closure locally
            self.position_manager.close_position(
                position.trade_id,
                current_price,
                reason="mean_reversion_complete",
                exit_order_id=exit_order_id,
            )
            
        except Exception as e:
            self.logger.error(f"Exit execution failed: {e}", exc_info=True)
    
    def run(self) -> None:
        """Main trading loop."""
        self.logger.info("Starting trading loop...")
        
        while self.running:
            try:
                self.run_trading_cycle()
                
                if self.running:
                    sleep_seconds = self.trading_config.sleep_seconds
                    minutes = sleep_seconds // 60
                    self.logger.info(f"Sleeping for {minutes} minutes...")
                    
                    # Sleep in smaller chunks to allow faster shutdown
                    for _ in range(sleep_seconds):
                        if not self.running:
                            break
                        time.sleep(1)
                        
            except KeyboardInterrupt:
                self.logger.info("Keyboard interrupt received")
                break
            except Exception as e:
                self.logger.error(f"Unexpected error in main loop: {e}", exc_info=True)
                time.sleep(60)  # Wait before retry
        
        # Cleanup
        self.logger.info("Shutting down...")
        self.position_manager.save_positions()
        self.logger.info("Shutdown complete")


def parse_args():
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(
        description="Live Trading Bot for Regime Reversion Strategy"
    )
    parser.add_argument(
        "--max-position",
        type=float,
        default=None,
        help="Maximum position size in USDT"
    )
    parser.add_argument(
        "--leverage",
        type=int,
        default=None,
        help="Trading leverage (1-125)"
    )
    parser.add_argument(
        "--interval",
        type=int,
        default=None,
        help="Trading cycle interval in seconds"
    )
    parser.add_argument(
        "--live",
        action="store_true",
        help="Use live trading mode (requires OKX_DEMO_MODE=false)"
    )
    return parser.parse_args()


def main():
    """Main entry point."""
    args = parse_args()
    
    # Load configuration
    okx_config, trading_config, path_config = get_config()
    
    # Apply command line overrides
    if args.max_position is not None:
        trading_config.max_position_usdt = args.max_position
    if args.leverage is not None:
        trading_config.leverage = args.leverage
    if args.interval is not None:
        trading_config.sleep_seconds = args.interval
    if args.live:
        okx_config.demo_mode = False
    
    # Setup logging
    logger = setup_logging(path_config.logs_dir)
    
    try:
        # Create and run bot
        bot = LiveTradingBot(okx_config, trading_config, path_config)
        bot.run()
    except ValueError as e:
        logger.error(f"Configuration error: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"Fatal error: {e}", exc_info=True)
        sys.exit(1)


if __name__ == "__main__":
    main()
