"""Base agent implementation for OpenClaw trading system.

This module provides the BaseAgent abstract class that all trading agents
must inherit from. It integrates economic tracking, event hooks, and
common agent functionality.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum, auto
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Protocol

from openclaw.core.economy import SurvivalStatus, TradingEconomicTracker
from openclaw.utils.logging import get_logger


class ActivityType(str, Enum):
    """Types of activities an agent can perform."""

    TRADE = "trade"
    LEARN = "learn"
    ANALYZE = "analyze"
    REST = "rest"
    PAPER_TRADE = "paper_trade"


@dataclass
class AgentState:
    """Current state of an agent."""

    agent_id: str
    skill_level: float = 0.5
    win_rate: float = 0.5
    total_trades: int = 0
    winning_trades: int = 0
    unlocked_factors: List[str] = field(default_factory=list)
    current_activity: Optional[ActivityType] = None
    learning_until: Optional[datetime] = None
    is_bankrupt: bool = False


class EventCallback(Protocol):
    """Protocol for event callback functions."""

    def __call__(self, agent: "BaseAgent", **kwargs: Any) -> None: ...


class BaseAgent(ABC):
    """Abstract base class for all trading agents.

    Each agent has its own economic tracker and must pay for its decisions.
    Agents can level up skills, unlock factors, and participate in trading.

    Args:
        agent_id: Unique identifier for this agent
        initial_capital: Starting balance for the agent
        skill_level: Initial skill level (0.0 to 1.0)
    """

    def __init__(
        self,
        agent_id: str,
        initial_capital: float,
        skill_level: float = 0.5,
    ):
        self.agent_id = agent_id
        self.economic_tracker = TradingEconomicTracker(
            agent_id=agent_id,
            initial_capital=initial_capital,
        )
        self.state = AgentState(
            agent_id=agent_id,
            skill_level=skill_level,
        )
        self.logger = get_logger(f"agents.{agent_id}")

        # Event hooks
        self._event_hooks: Dict[str, List[EventCallback]] = {
            "on_trade": [],
            "on_learn": [],
            "on_bankrupt": [],
            "on_level_up": [],
            "on_factor_unlock": [],
        }

        self.logger.info(
            f"Agent {agent_id} initialized with "
            f"${initial_capital:,.2f} capital, skill {skill_level:.1%}"
        )

    @property
    def balance(self) -> float:
        """Current balance from economic tracker."""
        return self.economic_tracker.balance

    @property
    def survival_status(self) -> SurvivalStatus:
        """Current survival status."""
        return self.economic_tracker.get_survival_status()

    @property
    def skill_level(self) -> float:
        """Current skill level."""
        return self.state.skill_level

    @property
    def win_rate(self) -> float:
        """Current win rate."""
        return self.state.win_rate

    def can_afford(self, amount: float, safety_buffer: float = 1.2) -> bool:
        """Check if agent can afford an expense with safety buffer.

        Args:
            amount: Required amount
            safety_buffer: Multiplier for safety margin (default 1.2 = 20% buffer)

        Returns:
            True if agent has sufficient funds
        """
        return self.balance >= amount * safety_buffer

    def check_survival(self) -> bool:
        """Check if agent is still solvent.

        Returns:
            False if agent is bankrupt
        """
        status = self.survival_status
        if status == SurvivalStatus.BANKRUPT:
            if not self.state.is_bankrupt:
                self.state.is_bankrupt = True
                self._trigger_event("on_bankrupt")
            return False
        return True

    def record_trade(self, is_win: bool, pnl: float) -> None:
        """Record a trade outcome and update statistics.

        Args:
            is_win: Whether the trade was profitable
            pnl: Profit/loss amount
        """
        self.state.total_trades += 1
        if is_win:
            self.state.winning_trades += 1

        # Update win rate
        if self.state.total_trades > 0:
            self.state.win_rate = self.state.winning_trades / self.state.total_trades

        # Trigger event
        self._trigger_event("on_trade", is_win=is_win, pnl=pnl)

        self.logger.info(
            f"Trade recorded: {'WIN' if is_win else 'LOSS'} ${abs(pnl):,.2f}, "
            f"win rate: {self.state.win_rate:.1%}"
        )

    def improve_skill(self, improvement: float) -> None:
        """Improve agent skill level.

        Args:
            improvement: Amount to improve (0.0 to 1.0)
        """
        old_level = self.state.skill_level
        self.state.skill_level = min(1.0, self.state.skill_level + improvement)

        if self.state.skill_level > old_level:
            self._trigger_event("on_level_up", old_level=old_level)
            self.logger.info(
                f"Skill improved: {old_level:.1%} -> {self.state.skill_level:.1%}"
            )

    def unlock_factor(self, factor_name: str, cost: float) -> bool:
        """Unlock a trading factor if affordable.

        Args:
            factor_name: Name of the factor to unlock
            cost: Cost to unlock

        Returns:
            True if successfully unlocked
        """
        if factor_name in self.state.unlocked_factors:
            return True

        if not self.can_afford(cost):
            self.logger.warning(
                f"Cannot afford factor '{factor_name}' (cost: ${cost:,.2f})"
            )
            return False

        # Deduct cost
        self.economic_tracker.balance -= cost
        self.state.unlocked_factors.append(factor_name)

        self._trigger_event("on_factor_unlock", factor_name=factor_name, cost=cost)
        self.logger.info(f"Unlocked factor: {factor_name} (${cost:,.2f})")

        return True

    def is_factor_unlocked(self, factor_name: str) -> bool:
        """Check if a factor is unlocked."""
        return factor_name in self.state.unlocked_factors

    def register_hook(self, event: str, callback: EventCallback) -> None:
        """Register an event hook callback.

        Args:
            event: Event name (on_trade, on_learn, on_bankrupt, etc.)
            callback: Function to call when event occurs
        """
        if event in self._event_hooks:
            self._event_hooks[event].append(callback)
        else:
            raise ValueError(f"Unknown event: {event}")

    def unregister_hook(self, event: str, callback: EventCallback) -> None:
        """Unregister an event hook callback."""
        if event in self._event_hooks and callback in self._event_hooks[event]:
            self._event_hooks[event].remove(callback)

    def _trigger_event(self, event: str, **kwargs: Any) -> None:
        """Trigger all callbacks for an event."""
        for callback in self._event_hooks.get(event, []):
            try:
                callback(self, **kwargs)
            except Exception as e:
                self.logger.error(f"Event hook error for {event}: {e}")

    @abstractmethod
    async def decide_activity(self) -> ActivityType:
        """Decide what activity to perform next.

        This is the core decision-making method that each agent must implement.
        The agent should consider its economic status, skill level, and
        market conditions.

        Returns:
            The activity type to perform
        """
        pass

    @abstractmethod
    async def analyze(self, symbol: str) -> Dict[str, Any]:
        """Analyze a trading symbol.

        Args:
            symbol: The symbol to analyze (e.g., "AAPL")

        Returns:
            Analysis results as a dictionary
        """
        pass

    def get_status_dict(self) -> Dict[str, Any]:
        """Get agent status as a dictionary for display/logging.

        Returns:
            Dictionary containing agent status
        """
        return {
            "agent_id": self.agent_id,
            "balance": self.balance,
            "status": self.survival_status.value,
            "skill_level": self.skill_level,
            "win_rate": self.win_rate,
            "total_trades": self.state.total_trades,
            "unlocked_factors": len(self.state.unlocked_factors),
            "is_bankrupt": self.state.is_bankrupt,
        }

    def __repr__(self) -> str:
        """String representation of the agent."""
        return (
            f"{self.__class__.__name__}("
            f"id={self.agent_id}, "
            f"balance=${self.balance:,.2f}, "
            f"status={self.survival_status.value}, "
            f"skill={self.skill_level:.1%}"
            f")"
        )
