"""Strategy scheduling system with time and condition-based triggers."""

from datetime import datetime, time
from typing import Optional, Callable, Dict, Any, List
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.interval import IntervalTrigger
from src.core.logger import get_logger
from src.core.database import get_database, Strategy
from src.strategies.base import get_strategy_registry, SignalType
from src.trading.engine import get_trading_engine
from src.data.pricing_service import get_pricing_service
from src.core.database import OrderSide, OrderType

logger = get_logger(__name__)


class StrategyScheduler:
    """Schedules strategy execution based on time and conditions."""
    
    def __init__(self):
        """Initialize strategy scheduler."""
        self.scheduler = BackgroundScheduler()
        self.scheduler.start()
        self.jobs: Dict[int, str] = {}  # strategy_id -> job_id
        self._active_strategies: Dict[int, Dict[str, Any]] = {}  # strategy_id -> status info
        self.logger = get_logger(__name__)
    
    def schedule_time_based(
        self,
        strategy_id: int,
        schedule_config: Dict[str, Any],
        callback: Callable
    ) -> bool:
        """Schedule strategy based on time.
        
        Args:
            strategy_id: Strategy ID
            schedule_config: Schedule configuration
                - type: 'daily', 'weekly', 'cron'
                - time: Time string (HH:MM) for daily
                - days: List of days for weekly
                - cron: Cron expression
            callback: Function to call
            
        Returns:
            True if scheduling successful
        """
        try:
            # Remove existing job if any
            if strategy_id in self.jobs:
                self.unschedule(strategy_id)
            
            schedule_type = schedule_config.get('type', 'daily')
            
            if schedule_type == 'daily':
                time_str = schedule_config.get('time', '09:00')
                hour, minute = map(int, time_str.split(':'))
                trigger = CronTrigger(hour=hour, minute=minute)
            elif schedule_type == 'weekly':
                days = schedule_config.get('days', [0, 1, 2, 3, 4])  # Monday-Friday
                time_str = schedule_config.get('time', '09:00')
                hour, minute = map(int, time_str.split(':'))
                trigger = CronTrigger(day_of_week=','.join(map(str, days)), hour=hour, minute=minute)
            elif schedule_type == 'cron':
                cron_expr = schedule_config.get('cron')
                trigger = CronTrigger.from_crontab(cron_expr)
            elif schedule_type == 'interval':
                interval = schedule_config.get('interval', 60)  # seconds
                trigger = IntervalTrigger(seconds=interval)
            else:
                logger.error(f"Unknown schedule type: {schedule_type}")
                return False
            
            job_id = f"strategy_{strategy_id}"
            self.scheduler.add_job(
                callback,
                trigger=trigger,
                id=job_id,
                replace_existing=True
            )
            
            self.jobs[strategy_id] = job_id
            logger.info(f"Scheduled strategy {strategy_id} with {schedule_type} trigger")
            return True
            
        except Exception as e:
            logger.error(f"Failed to schedule strategy {strategy_id}: {e}")
            return False
    
    def schedule_condition_based(
        self,
        strategy_id: int,
        condition: Callable[[], bool],
        callback: Callable,
        check_interval: int = 60
    ) -> bool:
        """Schedule strategy based on condition.
        
        Args:
            strategy_id: Strategy ID
            condition: Function that returns True when condition is met
            callback: Function to call when condition is met
            check_interval: Interval to check condition (seconds)
            
        Returns:
            True if scheduling successful
        """
        def check_and_execute():
            if condition():
                callback()
        
        return self.schedule_time_based(
            strategy_id,
            {'type': 'interval', 'interval': check_interval},
            check_and_execute
        )
    
    def unschedule(self, strategy_id: int):
        """Unschedule a strategy.
        
        Args:
            strategy_id: Strategy ID
        """
        if strategy_id in self.jobs:
            job_id = self.jobs[strategy_id]
            try:
                self.scheduler.remove_job(job_id)
                del self.jobs[strategy_id]
                logger.info(f"Unscheduled strategy {strategy_id}")
            except Exception as e:
                logger.error(f"Failed to unschedule strategy {strategy_id}: {e}")
    
    def is_scheduled(self, strategy_id: int) -> bool:
        """Check if strategy is scheduled.
        
        Args:
            strategy_id: Strategy ID
            
        Returns:
            True if scheduled
        """
        return strategy_id in self.jobs
    
    
    def start_strategy(self, strategy_id: int):
        """Start a strategy execution loop."""
        from sqlalchemy.orm import Session
        from sqlalchemy import create_engine
        from src.core.config import get_config
        
        config = get_config()
        db_url = config.get("database.url", "postgresql://localhost/crypto_trader")
        
        # Use sync engine for scheduler context
        engine = create_engine(db_url)
        
        with Session(engine) as session:
            try:
                strategy_model = session.query(Strategy).filter_by(id=strategy_id).first()
                if not strategy_model:
                    logger.error(f"Cannot start strategy {strategy_id}: Not found")
                    return

                # Instantiate strategy
                registry = get_strategy_registry()
                strategy_instance = registry.create_instance(
                    strategy_id=strategy_id,
                    name=strategy_model.strategy_type,
                    parameters=strategy_model.parameters,
                    timeframes=strategy_model.timeframes
                )
                
                if not strategy_instance:
                    logger.error(f"Failed to create instance for strategy {strategy_id}")
                    return

                strategy_instance.enabled = True
                
                # Store strategy info for status tracking
                self._active_strategies[strategy_id] = {
                    'instance': strategy_instance,
                    'name': strategy_model.name,
                    'type': strategy_model.strategy_type,
                    'symbol': strategy_model.parameters.get('symbol'),
                    'started_at': datetime.now(),
                    'last_tick': None,
                    'last_signal': None,
                    'signal_count': 0,
                    'error_count': 0,
                }
                
                # Use 'interval' from parameters, default 60s
                interval = strategy_model.parameters.get('interval', 60)
                
                def execute_wrapper():
                    self._execute_strategy_sync(strategy_id)

                self.schedule_time_based(
                    strategy_id,
                    {'type': 'interval', 'interval': interval},
                    execute_wrapper
                )
                logger.info(f"Started strategy {strategy_id} ({strategy_model.name})")

            except Exception as e:
                logger.error(f"Error initiating strategy {strategy_id}: {e}")

    def stop_strategy(self, strategy_id: int):
        """Stop a strategy."""
        self.unschedule(strategy_id)
        
        # Remove from active strategies
        if strategy_id in self._active_strategies:
            del self._active_strategies[strategy_id]
        
        logger.info(f"Stopped strategy {strategy_id}")

    def get_strategy_status(self, strategy_id: int) -> Optional[Dict[str, Any]]:
        """Get status of a running strategy."""
        if strategy_id not in self._active_strategies:
            return None
        
        info = self._active_strategies[strategy_id]
        return {
            'strategy_id': strategy_id,
            'name': info['name'],
            'type': info['type'],
            'symbol': info['symbol'],
            'running': True,
            'started_at': info['started_at'].isoformat() if info['started_at'] else None,
            'last_tick': info['last_tick'].isoformat() if info['last_tick'] else None,
            'last_signal': info['last_signal'],
            'signal_count': info['signal_count'],
            'error_count': info['error_count'],
        }

    def get_all_active_strategies(self) -> List[Dict[str, Any]]:
        """Get status of all active strategies."""
        return [self.get_strategy_status(sid) for sid in self._active_strategies.keys()]

    def _execute_strategy_sync(self, strategy_id: int):
        """Execute a single strategy cycle (synchronous wrapper)."""
        if strategy_id not in self._active_strategies:
            logger.warning(f"Strategy {strategy_id} not in active list, skipping execution")
            return
        
        info = self._active_strategies[strategy_id]
        strategy_instance = info['instance']
        
        try:
            # 1. Fetch Data
            symbol = strategy_instance.parameters.get('symbol')
            timeframe = strategy_instance.timeframes[0] if strategy_instance.timeframes else '1h'
            
            pricing_service = get_pricing_service()
            ticker = pricing_service.get_ticker(symbol)  # Sync call
            current_price = ticker.get('last')
            
            if not current_price:
                logger.debug(f"No price for {symbol}, skipping")
                return

            # Update last tick
            info['last_tick'] = datetime.now()
            
            # 2. Run async on_tick in sync context
            import asyncio
            try:
                loop = asyncio.get_event_loop()
            except RuntimeError:
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)
            
            signal = loop.run_until_complete(
                strategy_instance.on_tick(symbol, current_price, timeframe, ticker)
            )
            
            if not signal:
                logger.debug(f"Strategy {strategy_id}: No signal at price {current_price}")
                return
            
            # Update signal tracking
            info['last_signal'] = {
                'type': signal.signal_type.value,
                'strength': signal.strength,
                'price': float(signal.price),
                'timestamp': datetime.now().isoformat(),
                'metadata': signal.metadata,
            }
            info['signal_count'] += 1
            
            logger.info(f"Strategy {strategy_id} generated signal: {signal.signal_type.value} @ {current_price}")

            # 3. Handle Signal (Execution)
            trading_engine = get_trading_engine()
            
            # Check for Pairs Trading Metadata
            secondary_symbol = signal.metadata.get('secondary_symbol')
            secondary_action = signal.metadata.get('secondary_action')
            
            if secondary_symbol and secondary_action:
                # Multi-Leg Execution for Pairs Trading
                logger.info(f"Executing Pairs Trade: {symbol} ({signal.signal_type.value}) & {secondary_symbol} ({secondary_action})")
                
                # Execute Primary Leg
                loop.run_until_complete(trading_engine.execute_order(
                    exchange_id=1,
                    strategy_id=strategy_id,
                    symbol=symbol,
                    side=OrderSide(signal.signal_type.value),
                    order_type=OrderType.MARKET,
                    quantity=strategy_instance.calculate_position_size(
                        signal, trading_engine.paper_trading.get_balance(), current_price
                    ),
                    paper_trading=True
                ))
                
                # Execute Secondary Leg
                sec_ticker = pricing_service.get_ticker(secondary_symbol)
                sec_price = sec_ticker.get('last')
                if sec_price:
                    loop.run_until_complete(trading_engine.execute_order(
                        exchange_id=1,
                        strategy_id=strategy_id,
                        symbol=secondary_symbol,
                        side=OrderSide(secondary_action),
                        order_type=OrderType.MARKET,
                        quantity=strategy_instance.calculate_position_size(
                            signal, trading_engine.paper_trading.get_balance(), sec_price
                        ),
                        paper_trading=True,
                    ))
            
            else:
                # Standard Single Leg Execution
                loop.run_until_complete(trading_engine.execute_order(
                    exchange_id=1,
                    strategy_id=strategy_id,
                    symbol=symbol,
                    side=OrderSide(signal.signal_type.value),
                    order_type=OrderType.MARKET,
                    quantity=signal.quantity or strategy_instance.calculate_position_size(
                        signal, trading_engine.paper_trading.get_balance(), current_price
                    ),
                    paper_trading=True
                ))

        except Exception as e:
            logger.error(f"Strategy {strategy_id} execution error: {e}")
            info['error_count'] += 1

    def shutdown(self):
        """Shutdown scheduler."""
        self.scheduler.shutdown()


# Global scheduler
_scheduler: Optional[StrategyScheduler] = None


def get_scheduler() -> StrategyScheduler:
    """Get global strategy scheduler instance."""
    global _scheduler
    if _scheduler is None:
        _scheduler = StrategyScheduler()
    return _scheduler


