"""MarketAnalyst agent implementation for OpenClaw trading system.

This module provides the MarketAnalyst class that performs technical analysis
on market data, calculating indicators and generating structured reports.
"""

from dataclasses import dataclass
from typing import Any, Dict

import pandas as pd

from openclaw.agents.base import ActivityType, BaseAgent
from openclaw.core.economy import SurvivalStatus
from openclaw.indicators.technical import (
    bollinger_bands,
    ema,
    macd,
    rsi,
    sma,
)


@dataclass
class TechnicalReport:
    """Technical analysis report for a symbol.

    Attributes:
        symbol: The trading symbol analyzed
        trend: Identified trend ("uptrend", "downtrend", "sideways")
        indicators: Dictionary of calculated indicator values
        signals: Dictionary of trading signals ("buy", "sell", "neutral")
        confidence: Overall confidence score (0.0 to 1.0)
    """

    symbol: str
    trend: str
    indicators: Dict[str, Any]
    signals: Dict[str, str]
    confidence: float


class MarketAnalyst(BaseAgent):
    """Agent that performs technical analysis on market data.

    The MarketAnalyst calculates technical indicators (MA, EMA, RSI, MACD, Bollinger)
    and generates structured technical reports with trend identification and
    trading signals.

    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)
    """

    decision_cost = 0.05

    def __init__(
        self,
        agent_id: str,
        initial_capital: float,
        skill_level: float = 0.5,
    ):
        super().__init__(agent_id, initial_capital, skill_level)
        self._last_report: TechnicalReport | None = None

    async def decide_activity(self) -> ActivityType:
        """Decide what activity to perform based on economic status.

        Returns:
            The activity type to perform
        """
        status = self.survival_status

        # Bankrupt agents can only rest
        if status == SurvivalStatus.BANKRUPT:
            self.logger.warning("Agent is bankrupt, resting...")
            return ActivityType.REST

        # Critical status - focus on learning
        if status == SurvivalStatus.CRITICAL:
            if self.skill_level < 0.7:
                return ActivityType.LEARN
            return ActivityType.ANALYZE

        # Struggling - more analysis, less risk
        if status == SurvivalStatus.STRUGGLING:
            return ActivityType.ANALYZE

        # Default to analysis
        return ActivityType.ANALYZE

    async def analyze(self, symbol: str, data: pd.DataFrame | None = None) -> TechnicalReport:
        """Analyze a symbol and generate a technical report.

        Args:
            symbol: The trading symbol to analyze (e.g., "AAPL")
            data: Optional DataFrame with OHLCV data. If not provided,
                  analysis will use simulated data.

        Returns:
            TechnicalReport with indicators, trend, and signals
        """
        # Deduct decision cost
        self.economic_tracker.balance -= self.decision_cost
        self.logger.info(
            f"Analysis cost for {symbol}: ${self.decision_cost:.2f}"
        )

        # Generate or use provided data
        if data is None:
            data = self._generate_sample_data(symbol)

        # Calculate indicators
        indicators = self._calculate_indicators(data)

        # Identify trend
        trend = self._identify_trend(data, indicators)

        # Generate signals
        signals = self._generate_signals(indicators)

        # Calculate confidence
        confidence = self._calculate_confidence(indicators, signals)

        # Create report
        report = TechnicalReport(
            symbol=symbol,
            trend=trend,
            indicators=indicators,
            signals=signals,
            confidence=confidence,
        )

        self._last_report = report

        self.logger.info(
            f"Technical analysis for {symbol}: trend={trend}, "
            f"confidence={confidence:.1%}"
        )

        return report

    def _generate_sample_data(self, symbol: str) -> pd.DataFrame:
        """Generate sample price data for analysis.

        Args:
            symbol: The trading symbol

        Returns:
            DataFrame with sample OHLCV data
        """
        import numpy as np

        np.random.seed(42)
        n_periods = 100

        # Generate synthetic price data
        returns = np.random.normal(0.001, 0.02, n_periods)
        prices = 100 * np.exp(np.cumsum(returns))

        # Create OHLCV data
        data = pd.DataFrame(
            {
                "open": prices * (1 + np.random.normal(0, 0.001, n_periods)),
                "high": prices * (1 + abs(np.random.normal(0, 0.01, n_periods))),
                "low": prices * (1 - abs(np.random.normal(0, 0.01, n_periods))),
                "close": prices,
                "volume": np.random.randint(1000000, 10000000, n_periods),
            }
        )

        return data

    def _calculate_indicators(self, data: pd.DataFrame) -> Dict[str, Any]:
        """Calculate technical indicators.

        Args:
            data: DataFrame with price data

        Returns:
            Dictionary of indicator values
        """
        close = data["close"]

        # Moving averages
        ma_20 = sma(close, period=20)
        ma_50 = sma(close, period=50)
        ema_12 = ema(close, period=12)
        ema_26 = ema(close, period=26)

        # RSI
        rsi_values = rsi(close, period=14)

        # MACD
        macd_result = macd(close)

        # Bollinger Bands
        bb_result = bollinger_bands(close, period=20, std_dev=2.0)

        # Get latest values
        indicators = {
            "current_price": round(close.iloc[-1], 2),
            "ma_20": round(ma_20.iloc[-1], 2) if not pd.isna(ma_20.iloc[-1]) else None,
            "ma_50": round(ma_50.iloc[-1], 2) if not pd.isna(ma_50.iloc[-1]) else None,
            "ema_12": round(ema_12.iloc[-1], 2) if not pd.isna(ema_12.iloc[-1]) else None,
            "ema_26": round(ema_26.iloc[-1], 2) if not pd.isna(ema_26.iloc[-1]) else None,
            "rsi": round(rsi_values.iloc[-1], 2) if not pd.isna(rsi_values.iloc[-1]) else None,
            "macd": round(macd_result["macd"].iloc[-1], 4) if not pd.isna(macd_result["macd"].iloc[-1]) else None,
            "macd_signal": round(macd_result["signal"].iloc[-1], 4) if not pd.isna(macd_result["signal"].iloc[-1]) else None,
            "macd_histogram": round(macd_result["histogram"].iloc[-1], 4) if not pd.isna(macd_result["histogram"].iloc[-1]) else None,
            "bb_upper": round(bb_result["upper"].iloc[-1], 2) if not pd.isna(bb_result["upper"].iloc[-1]) else None,
            "bb_middle": round(bb_result["middle"].iloc[-1], 2) if not pd.isna(bb_result["middle"].iloc[-1]) else None,
            "bb_lower": round(bb_result["lower"].iloc[-1], 2) if not pd.isna(bb_result["lower"].iloc[-1]) else None,
        }

        return indicators

    def _identify_trend(
        self, data: pd.DataFrame, indicators: Dict[str, Any]
    ) -> str:
        """Identify the current trend based on indicators.

        Args:
            data: DataFrame with price data
            indicators: Dictionary of calculated indicators

        Returns:
            Trend description ("uptrend", "downtrend", "sideways")
        """
        current_price = indicators.get("current_price")
        ma_20 = indicators.get("ma_20")
        ma_50 = indicators.get("ma_50")
        ema_12 = indicators.get("ema_12")
        ema_26 = indicators.get("ema_26")

        # Need sufficient data for trend identification
        if None in [current_price, ma_20, ma_50, ema_12, ema_26]:
            return "insufficient_data"

        # Uptrend conditions
        price_above_mas = current_price > ma_20 and current_price > ma_50
        ema_bullish = ema_12 > ema_26
        ma_bullish = ma_20 > ma_50

        # Downtrend conditions
        price_below_mas = current_price < ma_20 and current_price < ma_50
        ema_bearish = ema_12 < ema_26
        ma_bearish = ma_20 < ma_50

        # Score the trend
        bullish_score = sum([price_above_mas, ema_bullish, ma_bullish])
        bearish_score = sum([price_below_mas, ema_bearish, ma_bearish])

        # Determine trend
        if bullish_score >= 2:
            return "uptrend"
        elif bearish_score >= 2:
            return "downtrend"
        else:
            return "sideways"

    def _generate_signals(self, indicators: Dict[str, Any]) -> Dict[str, str]:
        """Generate trading signals based on indicators.

        Args:
            indicators: Dictionary of calculated indicators

        Returns:
            Dictionary of signal types
        """
        signals = {
            "overall": "neutral",
            "rsi_signal": "neutral",
            "macd_signal": "neutral",
            "bb_signal": "neutral",
        }

        rsi_value = indicators.get("rsi")
        macd_value = indicators.get("macd")
        macd_signal_value = indicators.get("macd_signal")
        current_price = indicators.get("current_price")
        bb_upper = indicators.get("bb_upper")
        bb_lower = indicators.get("bb_lower")

        # RSI signals
        if rsi_value is not None:
            if rsi_value < 30:
                signals["rsi_signal"] = "buy"
            elif rsi_value > 70:
                signals["rsi_signal"] = "sell"
            else:
                signals["rsi_signal"] = "neutral"

        # MACD signals
        if macd_value is not None and macd_signal_value is not None:
            if macd_value > macd_signal_value:
                signals["macd_signal"] = "buy"
            elif macd_value < macd_signal_value:
                signals["macd_signal"] = "sell"
            else:
                signals["macd_signal"] = "neutral"

        # Bollinger Bands signals
        if current_price is not None and bb_upper is not None and bb_lower is not None:
            if current_price > bb_upper:
                signals["bb_signal"] = "sell"
            elif current_price < bb_lower:
                signals["bb_signal"] = "buy"
            else:
                signals["bb_signal"] = "neutral"

        # Overall signal - consensus of indicators
        buy_count = sum(
            1 for s in signals.values() if s == "buy"
        )
        sell_count = sum(
            1 for s in signals.values() if s == "sell"
        )

        if buy_count >= 2:
            signals["overall"] = "buy"
        elif sell_count >= 2:
            signals["overall"] = "sell"
        else:
            signals["overall"] = "neutral"

        return signals

    def _calculate_confidence(
        self, indicators: Dict[str, Any], signals: Dict[str, str]
    ) -> float:
        """Calculate confidence score for the analysis.

        Args:
            indicators: Dictionary of calculated indicators
            signals: Dictionary of trading signals

        Returns:
            Confidence score between 0.0 and 1.0
        """
        confidence = 0.5  # Base confidence

        # Adjust based on signal strength
        rsi_value = indicators.get("rsi")
        if rsi_value is not None:
            # Higher confidence when RSI is extreme
            rsi_deviation = abs(rsi_value - 50) / 50  # 0 to 1 scale
            confidence += rsi_deviation * 0.2

        # Adjust based on signal consensus
        if signals["overall"] != "neutral":
            # Check if multiple indicators agree
            indicator_signals = [
                signals["rsi_signal"],
                signals["macd_signal"],
                signals["bb_signal"],
            ]
            matching_signals = sum(
                1 for s in indicator_signals if s == signals["overall"]
            )
            confidence += matching_signals * 0.1

        # Skill level affects confidence accuracy
        confidence = confidence * (0.5 + self.skill_level * 0.5)

        return round(min(1.0, max(0.0, confidence)), 2)

    def get_last_report(self) -> TechnicalReport | None:
        """Get the most recent technical report.

        Returns:
            The last TechnicalReport or None if no analysis performed
        """
        return self._last_report
