"""
BaseAgent - Abstract base class for all trading agents
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime
from enum import Enum
import logging

logger = logging.getLogger(__name__)


class AgentStatus(Enum):
    """Agent execution status"""
    IDLE = "idle"
    RUNNING = "running"
    PAUSED = "paused"
    STOPPED = "stopped"
    ERROR = "error"


class RiskProfile(Enum):
    """Risk profile types"""
    CONSERVATIVE = "conservative"
    MODERATE = "moderate"
    AGGRESSIVE = "aggressive"


@dataclass
class Position:
    """Represents an open position"""
    symbol: str
    side: str  # 'buy' or 'sell'
    quantity: float
    entry_price: float
    current_price: float
    stop_loss: Optional[float] = None
    take_profit: Optional[float] = None
    opened_at: datetime = field(default_factory=datetime.now)
    pnl: float = 0.0
    pnl_percent: float = 0.0

    def update_pnl(self, current_price: float):
        """Update PnL based on current price"""
        self.current_price = current_price
        if self.side == 'buy':
            self.pnl = (current_price - self.entry_price) * self.quantity
        else:
            self.pnl = (self.entry_price - current_price) * self.quantity

        if self.entry_price > 0:
            self.pnl_percent = (self.pnl / (self.entry_price * self.quantity)) * 100


@dataclass
class AgentConfig:
    """Configuration for trading agents"""
    name: str
    profile: RiskProfile
    max_drawdown: float  # Maximum allowed drawdown (%)
    position_size_pct: float  # Position size as % of equity
    max_positions: int  # Maximum concurrent positions
    allowed_pairs: List[str]  # Trading pairs allowed
    strategies: List[str]  # Strategy names to use
    max_trades_per_day: int = 50
    min_equity: float = 100.0  # Minimum equity to operate
    stop_loss_pct: float = 2.0  # Default stop loss %
    take_profit_pct: float = 4.0  # Default take profit %


@dataclass
class AgentMetrics:
    """Agent performance metrics"""
    total_trades: int = 0
    winning_trades: int = 0
    losing_trades: int = 0
    total_profit: float = 0.0
    total_loss: float = 0.0
    max_drawdown: float = 0.0
    current_drawdown: float = 0.0
    win_rate: float = 0.0
    profit_factor: float = 0.0
    sharpe_ratio: float = 0.0
    trades_today: int = 0
    last_reset: datetime = field(default_factory=datetime.now)

    def update_win_rate(self):
        """Calculate win rate"""
        if self.total_trades > 0:
            self.win_rate = (self.winning_trades / self.total_trades) * 100

    def update_profit_factor(self):
        """Calculate profit factor"""
        if self.total_loss > 0:
            self.profit_factor = abs(self.total_profit / self.total_loss)
        else:
            self.profit_factor = float('inf') if self.total_profit > 0 else 0.0


class BaseAgent(ABC):
    """
    Abstract base class for all trading agents.
    Implements common functionality for position management,
    risk control, and strategy execution.
    """

    def __init__(self, config: AgentConfig):
        self.config = config
        self.positions: List[Position] = []
        self.equity: float = 0.0
        self.peak_equity: float = 0.0
        self.status: AgentStatus = AgentStatus.IDLE
        self.metrics = AgentMetrics()
        self.logger = logging.getLogger(f"{__name__}.{config.name}")

    async def start(self):
        """Start the trading agent"""
        self.logger.info(f"Starting agent: {self.config.name}")
        self.status = AgentStatus.RUNNING
        await self.on_start()

    async def stop(self):
        """Stop the trading agent"""
        self.logger.info(f"Stopping agent: {self.config.name}")
        self.status = AgentStatus.STOPPED
        await self.on_stop()

    async def pause(self):
        """Pause the trading agent"""
        self.logger.info(f"Pausing agent: {self.config.name}")
        self.status = AgentStatus.PAUSED
        await self.on_pause()

    async def resume(self):
        """Resume the trading agent"""
        self.logger.info(f"Resuming agent: {self.config.name}")
        self.status = AgentStatus.RUNNING
        await self.on_resume()

    def update_equity(self, new_equity: float):
        """Update agent equity and track peak"""
        self.equity = new_equity
        if new_equity > self.peak_equity:
            self.peak_equity = new_equity
        self._update_drawdown()

    def _update_drawdown(self):
        """Calculate current drawdown"""
        if self.peak_equity > 0:
            self.metrics.current_drawdown = ((self.peak_equity - self.equity) / self.peak_equity) * 100
            if self.metrics.current_drawdown > self.metrics.max_drawdown:
                self.metrics.max_drawdown = self.metrics.current_drawdown

    def can_open_position(self) -> bool:
        """Check if agent can open a new position"""
        # Check if agent is running
        if self.status != AgentStatus.RUNNING:
            return False

        # Check max positions
        if len(self.positions) >= self.config.max_positions:
            self.logger.warning(f"Max positions reached: {len(self.positions)}/{self.config.max_positions}")
            return False

        # Check max trades per day
        if self.metrics.trades_today >= self.config.max_trades_per_day:
            self.logger.warning(f"Max trades per day reached: {self.metrics.trades_today}")
            return False

        # Check minimum equity
        if self.equity < self.config.min_equity:
            self.logger.warning(f"Equity below minimum: {self.equity} < {self.config.min_equity}")
            return False

        # Check max drawdown
        if self.metrics.current_drawdown >= self.config.max_drawdown:
            self.logger.error(f"Max drawdown reached: {self.metrics.current_drawdown:.2f}% >= {self.config.max_drawdown}%")
            return False

        return True

    def add_position(self, position: Position):
        """Add a new position to the agent"""
        self.positions.append(position)
        self.logger.info(f"Position opened: {position.symbol} {position.side} {position.quantity} @ {position.entry_price}")

    def remove_position(self, symbol: str) -> Optional[Position]:
        """Remove and return a position"""
        for i, pos in enumerate(self.positions):
            if pos.symbol == symbol:
                removed = self.positions.pop(i)
                self.logger.info(f"Position closed: {symbol} PnL: {removed.pnl:.2f}")
                return removed
        return None

    def get_position(self, symbol: str) -> Optional[Position]:
        """Get position by symbol"""
        for pos in self.positions:
            if pos.symbol == symbol:
                return pos
        return None

    def record_trade(self, profit: float):
        """Record a completed trade"""
        self.metrics.total_trades += 1
        self.metrics.trades_today += 1

        if profit > 0:
            self.metrics.winning_trades += 1
            self.metrics.total_profit += profit
        else:
            self.metrics.losing_trades += 1
            self.metrics.total_loss += abs(profit)

        self.metrics.update_win_rate()
        self.metrics.update_profit_factor()

        self.logger.info(
            f"Trade recorded: Profit={profit:.2f}, "
            f"Total Trades={self.metrics.total_trades}, "
            f"Win Rate={self.metrics.win_rate:.2f}%"
        )

    def reset_daily_counters(self):
        """Reset daily trade counter"""
        self.metrics.trades_today = 0
        self.metrics.last_reset = datetime.now()
        self.logger.info("Daily counters reset")

    @abstractmethod
    async def on_start(self):
        """Called when agent starts"""
        pass

    @abstractmethod
    async def on_stop(self):
        """Called when agent stops"""
        pass

    async def on_pause(self):
        """Called when agent is paused"""
        pass

    async def on_resume(self):
        """Called when agent resumes"""
        pass

    @abstractmethod
    async def on_signal(self, signal: dict):
        """
        Process a trading signal from ML Engine

        Args:
            signal: Dict with keys like 'symbol', 'action', 'confidence', 'price', etc.
        """
        pass

    @abstractmethod
    async def evaluate_entry(self, signal: dict) -> bool:
        """
        Evaluate if a signal should be acted upon

        Args:
            signal: Trading signal to evaluate

        Returns:
            True if signal should be executed, False otherwise
        """
        pass

    async def check_risk_limits(self) -> bool:
        """
        Check if all risk limits are within acceptable ranges

        Returns:
            True if all checks pass, False otherwise
        """
        # Check drawdown
        if self.metrics.current_drawdown >= self.config.max_drawdown:
            self.logger.error(f"Drawdown limit exceeded: {self.metrics.current_drawdown:.2f}%")
            await self.pause()
            return False

        # Check minimum equity
        if self.equity < self.config.min_equity:
            self.logger.error(f"Equity below minimum: {self.equity:.2f}")
            await self.pause()
            return False

        return True

    def get_metrics(self) -> Dict:
        """Get current agent metrics as dict"""
        return {
            'name': self.config.name,
            'status': self.status.value,
            'equity': self.equity,
            'peak_equity': self.peak_equity,
            'current_drawdown': self.metrics.current_drawdown,
            'max_drawdown': self.metrics.max_drawdown,
            'total_trades': self.metrics.total_trades,
            'winning_trades': self.metrics.winning_trades,
            'losing_trades': self.metrics.losing_trades,
            'win_rate': self.metrics.win_rate,
            'total_profit': self.metrics.total_profit,
            'total_loss': self.metrics.total_loss,
            'profit_factor': self.metrics.profit_factor,
            'trades_today': self.metrics.trades_today,
            'positions': len(self.positions),
        }

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} "
            f"name={self.config.name} "
            f"status={self.status.value} "
            f"equity={self.equity:.2f} "
            f"positions={len(self.positions)}>"
        )
