"""Unit tests for BearResearcher agent.

This module tests the BearResearcher class including:
- BearReport generation
- Risk factor extraction
- Counter-argument generation
- Decision cost deduction
- Conviction level calculation
"""

import asyncio
from typing import Any, Dict
from unittest.mock import MagicMock

import pytest

from openclaw.agents.base import ActivityType
from openclaw.agents.bear_researcher import BearReport, BearResearcher
from openclaw.core.economy import SurvivalStatus


class TestBearReport:
    """Test BearReport dataclass."""

    def test_default_creation(self):
        """Test creating BearReport with default values."""
        report = BearReport(symbol="AAPL")

        assert report.symbol == "AAPL"
        assert report.risk_factors == []
        assert report.counter_arguments == {}
        assert report.downside_target == 0.0
        assert report.conviction_level == 0.0
        assert report.summary == ""

    def test_full_creation(self):
        """Test creating BearReport with all values."""
        report = BearReport(
            symbol="TSLA",
            risk_factors=["High volatility", "Competition"],
            counter_arguments={"Growth": "Slowing"},
            downside_target=150.0,
            conviction_level=0.75,
            summary="Bearish on TSLA",
        )

        assert report.symbol == "TSLA"
        assert len(report.risk_factors) == 2
        assert report.counter_arguments["Growth"] == "Slowing"
        assert report.downside_target == 150.0
        assert report.conviction_level == 0.75
        assert report.summary == "Bearish on TSLA"

    def test_conviction_level_capped(self):
        """Test conviction level is capped between 0 and 1."""
        # Above 1.0 should be capped
        report_high = BearReport(symbol="AAPL", conviction_level=1.5)
        assert report_high.conviction_level == 1.0

        # Below 0 should be floored
        report_low = BearReport(symbol="AAPL", conviction_level=-0.5)
        assert report_low.conviction_level == 0.0

    def test_to_dict(self):
        """Test conversion to dictionary."""
        report = BearReport(
            symbol="AAPL",
            risk_factors=["Risk 1"],
            counter_arguments={"Bull": "Counter"},
            downside_target=100.0,
            conviction_level=0.6,
            summary="Test summary",
        )

        data = report.to_dict()

        assert data["symbol"] == "AAPL"
        assert data["risk_factors"] == ["Risk 1"]
        assert data["counter_arguments"] == {"Bull": "Counter"}
        assert data["downside_target"] == 100.0
        assert data["conviction_level"] == 0.6
        assert data["summary"] == "Test summary"


class TestBearResearcherInitialization:
    """Test BearResearcher initialization."""

    def test_default_initialization(self):
        """Test agent with default parameters."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        assert agent.agent_id == "bear-1"
        assert agent.balance == 10000.0
        assert agent.skill_level == 0.5
        assert agent.decision_cost == 0.15
        assert agent._last_report is None

    def test_custom_initialization(self):
        """Test agent with custom skill level."""
        agent = BearResearcher(
            agent_id="bear-2",
            initial_capital=5000.0,
            skill_level=0.8,
        )

        assert agent.agent_id == "bear-2"
        assert agent.balance == 5000.0
        assert agent.skill_level == 0.8

    def test_repr(self):
        """Test string representation."""
        agent = BearResearcher(agent_id="bear-test", initial_capital=10000.0)

        repr_str = repr(agent)

        assert "BearResearcher" in repr_str
        assert "bear-test" in repr_str
        assert "$0.15" in repr_str or "0.15" in repr_str


class TestDecideActivity:
    """Test decide_activity method."""

    def test_bankrupt_agent_rest(self):
        """Test bankrupt agent can only rest."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        agent.economic_tracker.balance = 1000.0  # Below bankruptcy threshold

        result = asyncio.run(agent.decide_activity())

        assert result == ActivityType.REST

    def test_critical_agent_learns(self):
        """Test critical agent focuses on learning."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        agent.economic_tracker.balance = 3500.0  # Critical level

        result = asyncio.run(agent.decide_activity())

        assert result == ActivityType.LEARN

    def test_struggling_agent_paper_trades(self):
        """Test struggling agent does paper trading."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        agent.economic_tracker.balance = 8500.0  # Struggling level

        result = asyncio.run(agent.decide_activity())

        assert result == ActivityType.PAPER_TRADE

    def test_stable_agent_analyzes(self):
        """Test stable agent analyzes."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        agent.economic_tracker.balance = 12000.0  # Stable level

        result = asyncio.run(agent.decide_activity())

        assert result == ActivityType.ANALYZE


class TestDecisionCost:
    """Test decision cost deduction."""

    def test_decision_cost_deducted_in_analyze(self):
        """Test that decision cost is deducted during analysis."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        initial_balance = agent.balance

        asyncio.run(agent.analyze("AAPL"))

        # Balance should have decreased
        assert agent.balance < initial_balance

    def test_decision_cost_deducted_in_generate_bear_case(self):
        """Test that decision cost is deducted when generating bear case."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        initial_balance = agent.balance

        asyncio.run(agent.generate_bear_case("AAPL"))

        # Balance should have decreased
        assert agent.balance < initial_balance

    def test_cannot_afford_analysis(self):
        """Test behavior when agent cannot afford analysis."""
        # Start with balance below decision cost threshold
        agent = BearResearcher(agent_id="bear-1", initial_capital=0.10)

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        # Should return a report with insufficient funds message
        assert report.symbol == "AAPL"
        assert "insufficient" in report.summary.lower() or report.conviction_level == 0.0

    def test_decision_cost_constant(self):
        """Test that decision cost is set to $0.15."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        assert agent.decision_cost == 0.15


class TestGenerateBearCase:
    """Test generate_bear_case method."""

    def test_generates_bear_report(self):
        """Test that bear case generation returns BearReport."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        assert isinstance(report, BearReport)
        assert report.symbol == "AAPL"

    def test_report_contains_risk_factors(self):
        """Test that report contains risk factors."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=0.8)

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        assert len(report.risk_factors) > 0

    def test_report_contains_counter_arguments(self):
        """Test that report contains counter-arguments."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        assert len(report.counter_arguments) > 0

    def test_conviction_level_in_valid_range(self):
        """Test that conviction level is between 0 and 1."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        assert 0.0 <= report.conviction_level <= 1.0

    def test_conviction_based_on_skill(self):
        """Test that higher skill leads to higher conviction."""
        low_skill_agent = BearResearcher(
            agent_id="bear-low", initial_capital=10000.0, skill_level=0.3
        )
        high_skill_agent = BearResearcher(
            agent_id="bear-high", initial_capital=10000.0, skill_level=0.9
        )

        low_report = asyncio.run(low_skill_agent.generate_bear_case("AAPL"))
        high_report = asyncio.run(high_skill_agent.generate_bear_case("AAPL"))

        # Higher skill should generally lead to higher conviction
        # (may not always be true due to randomness in tests, but should trend this way)
        assert high_report.conviction_level >= low_report.conviction_level

    def test_with_technical_report(self):
        """Test bear case generation with technical report input."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=0.8)

        # Mock technical report
        tech_report = {"support_level": 150.0, "rsi": 75}

        report = asyncio.run(agent.generate_bear_case("AAPL", technical_report=tech_report))

        assert report.symbol == "AAPL"
        assert len(report.risk_factors) > 0
        assert len(report.counter_arguments) > 0

    def test_with_all_reports(self):
        """Test bear case generation with all report types."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=0.9)

        tech_report = {"support_level": 150.0}
        sentiment_report = {"sentiment": "bullish"}
        fundamental_report = {"pe_ratio": 30.0}

        report = asyncio.run(
            agent.generate_bear_case(
                "AAPL",
                technical_report=tech_report,
                sentiment_report=sentiment_report,
                fundamental_report=fundamental_report,
            )
        )

        assert report.symbol == "AAPL"
        assert len(report.risk_factors) >= 3  # Should have risks from all reports
        assert report.conviction_level > 0.4  # Higher conviction with more data

    def test_last_report_saved(self):
        """Test that last report is saved."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        assert agent.get_last_report() is None

        report = asyncio.run(agent.generate_bear_case("AAPL"))

        assert agent.get_last_report() is report


class TestExtractRiskFactors:
    """Test risk factor extraction."""

    def test_extract_from_technical_report(self):
        """Test extracting risks from technical report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=1.0)

        tech_report = MagicMock()
        risks = agent._extract_risk_factors(tech_report, None, None)

        assert len(risks) > 0
        # Should contain technical risks
        assert any("RSI" in risk or "support" in risk.lower() for risk in risks)

    def test_extract_from_sentiment_report(self):
        """Test extracting risks from sentiment report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=1.0)

        sentiment_report = MagicMock()
        risks = agent._extract_risk_factors(None, sentiment_report, None)

        assert len(risks) > 0

    def test_extract_from_fundamental_report(self):
        """Test extracting risks from fundamental report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=1.0)

        fundamental_report = MagicMock()
        risks = agent._extract_risk_factors(None, None, fundamental_report)

        assert len(risks) > 0

    def test_generic_risks_when_no_reports(self):
        """Test generic risks when no reports provided."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=0.5)

        risks = agent._extract_risk_factors(None, None, None)

        assert len(risks) > 0
        # Should have generic risks
        assert any("volatility" in risk.lower() or "uncertainty" in risk.lower() for risk in risks)


class TestGenerateCounterArguments:
    """Test counter-argument generation."""

    def test_counters_with_technical(self):
        """Test counter-arguments with technical report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        tech_report = MagicMock()
        counters = agent._generate_counter_arguments(tech_report, None, None)

        assert len(counters) > 0

    def test_counters_with_fundamental(self):
        """Test counter-arguments with fundamental report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        fundamental_report = MagicMock()
        counters = agent._generate_counter_arguments(None, None, fundamental_report)

        assert len(counters) > 0
        # Should contain fundamental counters
        assert any("growth" in k.lower() or "valuation" in k.lower() for k in counters.keys())

    def test_generic_counters_when_no_reports(self):
        """Test generic counter-arguments when no reports."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        counters = agent._generate_counter_arguments(None, None, None)

        assert len(counters) > 0


class TestCounterBullishPoint:
    """Test counter_bullish_point method."""

    def test_counter_strong_growth(self):
        """Test counter to strong growth argument."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        counter = agent.counter_bullish_point("strong growth")

        assert "peaking" in counter.lower() or "growth" in counter.lower()

    def test_counter_undervalued(self):
        """Test counter to undervalued argument."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        counter = agent.counter_bullish_point("undervalued")

        assert "value trap" in counter.lower() or "value" in counter.lower()

    def test_counter_market_leader(self):
        """Test counter to market leader argument."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        counter = agent.counter_bullish_point("market leader")

        assert "competition" in counter.lower() or "leader" in counter.lower()

    def test_generic_counter_for_unknown(self):
        """Test generic counter for unknown bullish point."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        counter = agent.counter_bullish_point("some random bullish point xyz")

        # Should return a generic counter-argument
        assert len(counter) > 0


class TestCalculateConviction:
    """Test conviction calculation."""

    def test_conviction_based_on_skill(self):
        """Test conviction is influenced by skill level."""
        low_skill = BearResearcher(agent_id="bear-low", initial_capital=10000.0, skill_level=0.3)
        high_skill = BearResearcher(agent_id="bear-high", initial_capital=10000.0, skill_level=0.9)

        low_conviction = low_skill._calculate_conviction([], None, None, None)
        high_conviction = high_skill._calculate_conviction([], None, None, None)

        # Higher skill should generally lead to higher base conviction
        assert high_conviction > low_conviction

    def test_conviction_with_more_risks(self):
        """Test conviction increases with more risk factors."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=0.5)

        few_risks = ["Risk 1"]
        many_risks = ["Risk 1", "Risk 2", "Risk 3", "Risk 4"]

        few_conviction = agent._calculate_conviction(few_risks, None, None, None)
        many_conviction = agent._calculate_conviction(many_risks, None, None, None)

        # More risks should generally lead to higher conviction
        assert many_conviction >= few_conviction

    def test_conviction_capped(self):
        """Test conviction is capped at 0.9."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0, skill_level=1.0)

        # Create many risk factors and reports to maximize conviction
        many_risks = [f"Risk {i}" for i in range(20)]
        tech_report = MagicMock()
        sentiment_report = MagicMock()
        fundamental_report = MagicMock()

        conviction = agent._calculate_conviction(
            many_risks, tech_report, sentiment_report, fundamental_report
        )

        # Should be capped at 0.9 for bearish views
        assert conviction <= 0.9


class TestGenerateSummary:
    """Test summary generation."""

    def test_summary_based_on_conviction(self):
        """Test summary tone changes based on conviction."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        low_summary = agent._generate_summary("AAPL", [], 0.3)
        medium_summary = agent._generate_summary("AAPL", [], 0.5)
        high_summary = agent._generate_summary("AAPL", [], 0.8)

        # Different conviction levels should produce different tones
        assert "Mildly cautious" in low_summary or "cautious" in low_summary.lower()
        assert "Bearish" in high_summary or "bearish" in high_summary.lower()

    def test_summary_includes_symbol(self):
        """Test summary includes the symbol."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        summary = agent._generate_summary("TSLA", ["Risk 1", "Risk 2"], 0.6)

        assert "TSLA" in summary

    def test_summary_includes_risk_count(self):
        """Test summary includes risk count."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        risks = ["Risk 1", "Risk 2", "Risk 3"]
        summary = agent._generate_summary("AAPL", risks, 0.6)

        assert "3" in summary or "three" in summary.lower()


class TestAnalyzeMethod:
    """Test the analyze method."""

    def test_analyze_returns_dict(self):
        """Test analyze returns a dictionary."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        result = asyncio.run(agent.analyze("AAPL"))

        assert isinstance(result, dict)
        assert result["symbol"] == "AAPL"

    def test_analyze_deducts_cost(self):
        """Test analyze deducts decision cost."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)
        initial_balance = agent.balance

        asyncio.run(agent.analyze("AAPL"))

        assert agent.balance < initial_balance

    def test_analyze_contains_bear_report(self):
        """Test analyze result contains bear report."""
        agent = BearResearcher(agent_id="bear-1", initial_capital=10000.0)

        result = asyncio.run(agent.analyze("AAPL"))

        assert "bear_report" in result
        assert result["bear_report"]["symbol"] == "AAPL"
