"""BearResearcher agent implementation for OpenClaw trading system.

This module provides the BearResearcher agent class that generates bearish
viewpoints by analyzing risk factors and countering bullish arguments.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from openclaw.agents.base import ActivityType, BaseAgent
from openclaw.core.economy import SurvivalStatus


@dataclass
class BearReport:
    """Bearish research report containing risk analysis and counter-arguments.

    Attributes:
        symbol: The stock symbol being analyzed
        risk_factors: List of identified risk factors
        counter_arguments: Dict mapping bullish points to their counter-arguments
        downside_target: Estimated downside price target
        conviction_level: Confidence level in the bearish view (0.0 to 1.0)
        summary: Overall bearish thesis summary
    """

    symbol: str
    risk_factors: List[str] = field(default_factory=list)
    counter_arguments: Dict[str, str] = field(default_factory=dict)
    downside_target: float = 0.0
    conviction_level: float = 0.0
    summary: str = ""

    def __post_init__(self):
        """Validate conviction level is within valid range."""
        self.conviction_level = max(0.0, min(1.0, self.conviction_level))

    def to_dict(self) -> Dict[str, Any]:
        """Convert report to dictionary format."""
        return {
            "symbol": self.symbol,
            "risk_factors": self.risk_factors,
            "counter_arguments": self.counter_arguments,
            "downside_target": self.downside_target,
            "conviction_level": self.conviction_level,
            "summary": self.summary,
        }


class BearResearcher(BaseAgent):
    """Research agent that generates bearish viewpoints and risk analysis.

    The BearResearcher analyzes technical, sentiment, and fundamental reports
to identify risks and generate counter-arguments to bullish views.

    Decision cost: $0.15 per bearish analysis.

    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: float = 0.15

    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: Optional[BearReport] = 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(f"Agent {self.agent_id} is bankrupt, resting...")
            return ActivityType.REST

        # Critical status - focus on learning
        if status == SurvivalStatus.CRITICAL:
            return ActivityType.LEARN

        # Struggling - more paper trading
        if status == SurvivalStatus.STRUGGLING:
            return ActivityType.PAPER_TRADE

        # Stable or thriving - analyze
        return ActivityType.ANALYZE

    async def analyze(self, symbol: str) -> Dict[str, Any]:
        """Analyze a trading symbol and return bearish analysis.

        This is a simplified analysis that generates a basic bear report.
        For full analysis with external reports, use generate_bear_case().

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

        Returns:
            Dictionary containing bearish analysis results
        """
        # Deduct decision cost
        await self._deduct_decision_cost()

        # Generate basic bear report
        report = BearReport(
            symbol=symbol,
            risk_factors=["Market volatility", "Economic uncertainty"],
            counter_arguments={"Bull run": "May be overextended"},
            downside_target=0.0,  # Would be calculated from actual data
            conviction_level=0.3 + (self.skill_level * 0.2),  # Base + skill bonus
            summary=f"Cautious outlook for {symbol}. Monitor risks closely.",
        )

        self._last_report = report

        return {
            "symbol": symbol,
            "bear_report": report.to_dict(),
            "agent_id": self.agent_id,
            "skill_level": self.skill_level,
        }

    async def generate_bear_case(
        self,
        symbol: str,
        technical_report: Optional[Any] = None,
        sentiment_report: Optional[Any] = None,
        fundamental_report: Optional[Any] = None,
    ) -> BearReport:
        """Generate a comprehensive bearish case based on analyst reports.

        This method analyzes multiple report types to identify risks and
        generate counter-arguments to bullish views.

        Args:
            symbol: The stock symbol to analyze
            technical_report: Optional technical analysis report
            sentiment_report: Optional sentiment analysis report
            fundamental_report: Optional fundamental analysis report

        Returns:
            BearReport containing the bearish analysis
        """
        # Check if agent can afford the analysis
        if not self.can_afford(self.decision_cost):
            self.logger.warning(
                f"Cannot afford bear case analysis for {symbol} "
                f"(cost: ${self.decision_cost:.2f}, balance: ${self.balance:.2f})"
            )
            return BearReport(
                symbol=symbol,
                summary="Insufficient funds for analysis",
                conviction_level=0.0,
            )

        # Deduct decision cost
        await self._deduct_decision_cost()

        # Extract risk factors from reports
        risk_factors = self._extract_risk_factors(
            technical_report, sentiment_report, fundamental_report
        )

        # Generate counter-arguments
        counter_arguments = self._generate_counter_arguments(
            technical_report, sentiment_report, fundamental_report
        )

        # Calculate downside target based on skill and available data
        downside_target = self._calculate_downside_target(
            symbol, technical_report, fundamental_report
        )

        # Calculate conviction level based on data quality and skill
        conviction_level = self._calculate_conviction(
            risk_factors, technical_report, sentiment_report, fundamental_report
        )

        # Generate summary
        summary = self._generate_summary(symbol, risk_factors, conviction_level)

        report = BearReport(
            symbol=symbol,
            risk_factors=risk_factors,
            counter_arguments=counter_arguments,
            downside_target=downside_target,
            conviction_level=conviction_level,
            summary=summary,
        )

        self._last_report = report

        self.logger.info(
            f"Generated bear case for {symbol}: "
            f"conviction={conviction_level:.1%}, "
            f"risks={len(risk_factors)}, "
            f"counters={len(counter_arguments)}"
        )

        return report

    async def _deduct_decision_cost(self) -> float:
        """Deduct the decision cost from the agent's balance.

        Returns:
            The cost that was deducted
        """
        # Use a fixed cost calculation based on decision_cost
        # Using a simplified cost structure: estimate tokens based on decision_cost
        # $0.15 ~ 1500 tokens at typical rates
        tokens_estimate = int(self.decision_cost * 10000)

        cost = self.economic_tracker.calculate_decision_cost(
            tokens_input=tokens_estimate,
            tokens_output=int(tokens_estimate * 0.4),  # 40% output ratio
            market_data_calls=1,  # One market data call for context
        )

        self.logger.debug(f"Deducted decision cost: ${cost:.4f}")
        return cost

    def _extract_risk_factors(
        self,
        technical_report: Optional[Any],
        sentiment_report: Optional[Any],
        fundamental_report: Optional[Any],
    ) -> List[str]:
        """Extract risk factors from analyst reports.

        Args:
            technical_report: Optional technical analysis report
            sentiment_report: Optional sentiment analysis report
            fundamental_report: Optional fundamental analysis report

        Returns:
            List of identified risk factors
        """
        risk_factors: List[str] = []

        # Extract from technical report
        if technical_report is not None:
            # Common technical risk indicators
            tech_risks = [
                "Overbought conditions (RSI > 70)",
                "Death cross formation",
                "Breaking below support levels",
                "High volatility environment",
                "Volume divergence from price",
            ]
            # Add more risks based on higher skill
            num_tech_risks = max(1, int(self.skill_level * len(tech_risks)))
            risk_factors.extend(tech_risks[:num_tech_risks])

        # Extract from sentiment report
        if sentiment_report is not None:
            sentiment_risks = [
                "Excessive bullish sentiment (contrarian signal)",
                "Insider selling activity",
                "Decreasing institutional ownership",
                "Social media hype unsustainable",
            ]
            num_sentiment_risks = max(1, int(self.skill_level * len(sentiment_risks)))
            risk_factors.extend(sentiment_risks[:num_sentiment_risks])

        # Extract from fundamental report
        if fundamental_report is not None:
            fundamental_risks = [
                "High debt-to-equity ratio",
                "Declining profit margins",
                "Increasing competition pressure",
                "Regulatory headwinds",
                "Cyclical downturn exposure",
            ]
            num_fund_risks = max(1, int(self.skill_level * len(fundamental_risks)))
            risk_factors.extend(fundamental_risks[:num_fund_risks])

        # If no reports provided, provide generic risks based on skill
        if not risk_factors:
            generic_risks = [
                "Market volatility",
                "Economic uncertainty",
                "Sector rotation risk",
            ]
            num_generic = max(1, int(self.skill_level * len(generic_risks)))
            risk_factors.extend(generic_risks[:num_generic])

        return risk_factors

    def _generate_counter_arguments(
        self,
        technical_report: Optional[Any],
        sentiment_report: Optional[Any],
        fundamental_report: Optional[Any],
    ) -> Dict[str, str]:
        """Generate counter-arguments to common bullish points.

        Args:
            technical_report: Optional technical analysis report
            sentiment_report: Optional sentiment analysis report
            fundamental_report: Optional fundamental analysis report

        Returns:
            Dict mapping bullish points to their counter-arguments
        """
        counter_arguments: Dict[str, str] = (
            {}
        )

        # Technical counter-arguments
        if technical_report is not None:
            counter_arguments.update(
                {
                    "Strong uptrend": "Trend may be overextended, reversal risk elevated",
                    "Breaking resistance": "False breakout possible, volume confirmation lacking",
                    "Golden cross": "Lagging indicator, often signals late in move",
                }
            )

        # Sentiment counter-arguments
        if sentiment_report is not None:
            counter_arguments.update(
                {
                    "Positive news flow": "News may be priced in, sell-the-news risk",
                    "Analyst upgrades": "Upgrades often lag price, not predictive",
                    "High social sentiment": "Contrarian indicator at extremes",
                }
            )

        # Fundamental counter-arguments
        if fundamental_report is not None:
            counter_arguments.update(
                {
                    "Strong earnings growth": "Growth may be peaking, tough comps ahead",
                    "Low valuation": "Value trap risk if fundamentals deteriorate",
                    "Market share gains": "Gains may be unsustainable, competition responding",
                    "Share buybacks": "May mask lack of growth opportunities",
                }
            )

        # If no reports, provide generic counters
        if not counter_arguments:
            counter_arguments = {
                "Bull market": "All bull markets eventually correct or reverse",
                "Past performance": "Past performance doesn't guarantee future results",
            }

        return counter_arguments

    def _calculate_downside_target(
        self,
        symbol: str,
        technical_report: Optional[Any],
        fundamental_report: Optional[Any],
    ) -> float:
        """Calculate estimated downside price target.

        Args:
            symbol: The stock symbol
            technical_report: Optional technical analysis report
            fundamental_report: Optional fundamental analysis report

        Returns:
            Estimated downside price target
        """
        # This is a simplified calculation
        # In a real implementation, this would use actual price data and
        # support levels from the technical report

        base_downside = 0.0

        # If we have technical data, use support levels
        if technical_report is not None:
            # Try to extract support level from report
            if hasattr(technical_report, "support_level"):
                base_downside = technical_report.support_level * 0.95  # 5% below support
            elif isinstance(technical_report, dict):
                base_downside = technical_report.get("support_level", 0) * 0.95

        # Adjust based on skill level (higher skill = more precise target)
        if base_downside > 0:
            # Add skill-based adjustment
            skill_adjustment = (self.skill_level - 0.5) * 0.1  # +/- 5% based on skill
            return base_downside * (1 + skill_adjustment)

        # Default: return 0 to indicate target not available
        return 0.0

    def _calculate_conviction(
        self,
        risk_factors: List[str],
        technical_report: Optional[Any],
        sentiment_report: Optional[Any],
        fundamental_report: Optional[Any],
    ) -> float:
        """Calculate conviction level based on data quality and analysis.

        Args:
            risk_factors: List of identified risk factors
            technical_report: Optional technical analysis report
            sentiment_report: Optional sentiment analysis report
            fundamental_report: Optional fundamental analysis report

        Returns:
            Conviction level between 0.0 and 1.0
        """
        # Base conviction from skill level
        base_conviction = 0.3 + (self.skill_level * 0.3)  # 0.3 to 0.6

        # More reports = higher conviction potential
        report_count = sum(
            [
                technical_report is not None,
                sentiment_report is not None,
                fundamental_report is not None,
            ]
        )
        report_bonus = report_count * 0.1  # +0.1 per report

        # More risk factors = higher conviction (up to a point)
        risk_bonus = min(len(risk_factors) * 0.05, 0.15)  # Max +0.15

        conviction = base_conviction + report_bonus + risk_bonus

        # Cap at 0.9 for bearish views (maintain some humility)
        return min(conviction, 0.9)

    def _generate_summary(
        self, symbol: str, risk_factors: List[str], conviction_level: float
    ) -> str:
        """Generate a summary of the bearish thesis.

        Args:
            symbol: The stock symbol
            risk_factors: List of identified risk factors
            conviction_level: The conviction level

        Returns:
            Summary string
        """
        if conviction_level < 0.4:
            stance = "Mildly cautious"
        elif conviction_level < 0.6:
            stance = "Moderately bearish"
        elif conviction_level < 0.8:
            stance = "Bearish"
        else:
            stance = "Strongly bearish"

        risk_summary = f"{len(risk_factors)} key risks identified"

        return (
            f"{stance} on {symbol}. {risk_summary}. "
            f"Risk-reward favors caution. "
            f"Monitor for breakdown confirmation."
        )

    def get_last_report(self) -> Optional[BearReport]:
        """Get the most recently generated bear report.

        Returns:
            The last BearReport or None if no report generated
        """
        return self._last_report

    def counter_bullish_point(self, bullish_point: str) -> str:
        """Generate a counter-argument to a specific bullish point.

        Args:
            bullish_point: The bullish argument to counter

        Returns:
            Counter-argument string
        """
        # Common bullish points and their counters
        counters = {
            "strong growth": "Growth may be peaking, difficult year-over-year comparisons ahead",
            "undervalued": "May be a value trap if fundamentals are deteriorating",
            "market leader": "Leadership position attracts competition and regulatory scrutiny",
            "innovative": "Innovation requires continuous investment with uncertain returns",
            "high margins": "High margins attract competition and may not be sustainable",
            "moat": "Economic moats can erode over time due to disruption",
            "buyback": "Buybacks may signal lack of better investment opportunities",
            "dividend": "High dividend yield may indicate distress, not strength",
            "beating earnings": "Earnings beats may already be priced in or quality is declining",
            "upgrade": "Analyst upgrades often follow price, not predict it",
            "momentum": "Momentum can reverse quickly, especially at extremes",
            "breakout": "False breakouts common, wait for confirmation",
        }

        # Find matching counter or generate generic
        bullish_lower = bullish_point.lower()
        for key, counter in counters.items():
            if key in bullish_lower:
                return counter

        return f"Alternative view: {bullish_point} may not account for changing market conditions or competition"

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