""""Unit tests for RiskManager agent.

This module tests the RiskManager class including risk assessment,
VaR calculation, portfolio risk metrics, and decision cost deduction.
"""

import asyncio
from unittest.mock import patch

import pytest

from openclaw.agents.base import ActivityType
from openclaw.agents.risk_manager import (
    PortfolioRiskMetrics,
    RiskManager,
    RiskReport,
)
from openclaw.core.economy import SurvivalStatus


class TestRiskManagerInitialization:
    """"Test RiskManager initialization."""

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

        assert agent.agent_id == "risk-1"
        assert agent.balance == 10000.0
        assert agent.skill_level == 0.5
        assert agent.max_risk_per_trade == 0.02
        assert agent.max_portfolio_var == 0.05
        assert agent.decision_cost == 0.20
        assert agent._risk_history == []
        assert agent._portfolio_risk_history == []

    def test_custom_initialization(self):
        """"Test agent with custom parameters."""
        agent = RiskManager(
            agent_id="risk-2",
            initial_capital=5000.0,
            skill_level=0.8,
            max_risk_per_trade=0.03,
            max_portfolio_var=0.08,
        )

        assert agent.agent_id == "risk-2"
        assert agent.balance == 5000.0
        assert agent.skill_level == 0.8
        assert agent.max_risk_per_trade == 0.03
        assert agent.max_portfolio_var == 0.08

    def test_inherits_from_base_agent(self):
        """"Test that RiskManager inherits from BaseAgent."""
        from openclaw.agents.base import BaseAgent

        agent = RiskManager(agent_id="test", initial_capital=10000.0)

        assert isinstance(agent, BaseAgent)


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

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_bankrupt_agent_only_rests(self, agent):
        """"Test that bankrupt agent can only rest."""
        agent.economic_tracker.balance = 0  # Bankrupt

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

        assert result == ActivityType.REST

    def test_critical_status_prefers_learning(self, agent):
        """"Test critical status leads to learning."""
        agent.economic_tracker.balance = 3500.0  # Critical
        agent.state.skill_level = 0.5

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

        assert result in [ActivityType.LEARN, ActivityType.ANALYZE]

    def test_thriving_status_prefers_analyzing(self, agent):
        """"Test thriving status leads to analyzing."""
        agent.economic_tracker.balance = 20000.0  # Thriving

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

        assert result == ActivityType.ANALYZE

    def test_low_skill_leads_to_learning(self, agent):
        """"Test that low skill level leads to learning."""
        agent.economic_tracker.balance = 15000.0  # Stable
        agent.state.skill_level = 0.3

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

        assert result == ActivityType.LEARN


class TestAssessRisk:
    """"Test assess_risk method."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_returns_risk_report(self, agent):
        """"Test that assess_risk returns a RiskReport."""
        result = asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))

        assert isinstance(result, RiskReport)
        assert result.symbol == "AAPL"
        assert result.risk_level in ["low", "medium", "high", "extreme"]
        assert result.volatility > 0
        assert result.var_95 >= 0
        assert result.var_99 >= 0

    def test_deducts_decision_cost(self, agent):
        """"Test that assess_risk deducts the $0.20 decision cost."""
        initial_balance = agent.balance

        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))

        assert agent.balance == initial_balance - 0.20

    def test_risk_report_contains_warnings_for_high_volatility(self, agent):
        """"Test that high volatility generates warnings."""
        with patch.object(agent, '_estimate_volatility', return_value=0.60):
            result = asyncio.run(agent.assess_risk("TSLA", position_size=5000.0))

        assert len(result.warnings) > 0
        assert any("volatility" in w.lower() for w in result.warnings)

    def test_position_size_recommendation(self, agent):
        """"Test that position size recommendation is calculated."""
        result = asyncio.run(agent.assess_risk("AAPL", position_size=5000.0))

        assert result.position_size_recommendation >= 0

    def test_extreme_volatility_reduces_recommendation(self, agent):
        """"Test that extreme volatility significantly reduces recommendation."""
        with patch.object(agent, '_estimate_volatility', return_value=0.60):
            result = asyncio.run(agent.assess_risk("TSLA", position_size=5000.0))

        # Should recommend much less than requested due to high volatility
        assert result.position_size_recommendation < 5000.0

    def test_cannot_afford_assessment(self, agent):
        """"Test behavior when agent cannot afford assessment."""
        agent.economic_tracker.balance = 0.10  # Less than decision cost

        result = asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))

        assert result.risk_level == "extreme"
        assert "cannot afford" in result.warnings[0].lower()

    def test_risk_history_updated(self, agent):
        """"Test that risk assessment is recorded in history."""
        initial_history_len = len(agent._risk_history)

        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))

        assert len(agent._risk_history) == initial_history_len + 1
        assert agent._risk_history[-1].symbol == "AAPL"


class TestAnalyze:
    """"Test analyze method (async)."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_analyze_returns_dict(self, agent):
        """"Test that analyze returns a dictionary."""
        result = asyncio.run(agent.analyze("AAPL"))

        assert isinstance(result, dict)
        assert result["symbol"] == "AAPL"
        assert "risk_level" in result
        assert "volatility" in result
        assert "var_95" in result
        assert "var_99" in result
        assert "cost" in result

    def test_analyze_deducts_cost(self, agent):
        """"Test that analyze deducts decision cost."""
        initial_balance = agent.balance

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

        assert agent.balance < initial_balance


class TestPortfolioRisk:
    """"Test portfolio risk assessment methods."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_assess_portfolio_risk_returns_metrics(self, agent):
        """"Test that assess_portfolio_risk returns PortfolioRiskMetrics."""
        positions = {"AAPL": 3000.0, "GOOGL": 2000.0, "MSFT": 2500.0}

        result = agent.assess_portfolio_risk("portfolio-1", positions)

        assert isinstance(result, PortfolioRiskMetrics)
        assert result.portfolio_id == "portfolio-1"
        assert result.total_exposure == 7500.0
        assert 0 <= result.concentration_risk <= 1
        assert 0 <= result.correlation_risk <= 1
        assert result.portfolio_var_95 >= 0
        assert result.portfolio_var_99 >= 0

    def test_empty_portfolio(self, agent):
        """"Test portfolio risk with empty positions."""
        result = agent.assess_portfolio_risk("empty-portfolio", {})

        assert result.total_exposure == 0.0
        assert result.concentration_risk == 0.0
        assert result.portfolio_var_95 == 0.0

    def test_single_position_portfolio(self, agent):
        """"Test portfolio risk with single position."""
        positions = {"AAPL": 5000.0}

        result = agent.assess_portfolio_risk("single-portfolio", positions)

        assert result.total_exposure == 5000.0
        assert result.concentration_risk == 1.0  # Fully concentrated

    def test_portfolio_history_updated(self, agent):
        """"Test that portfolio assessment is recorded in history."""
        initial_history_len = len(agent._portfolio_risk_history)

        agent.assess_portfolio_risk("portfolio-1", {"AAPL": 1000.0})

        assert len(agent._portfolio_risk_history) == initial_history_len + 1


class TestVolatilityEstimation:
    """"Test volatility estimation methods."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_volatility_in_valid_range(self, agent):
        """"Test that estimated volatility is in valid range."""
        volatility = agent._estimate_volatility("AAPL")

        assert 0 < volatility <= 1.0

    def test_high_vol_symbols_have_higher_volatility(self, agent):
        """"Test that high volatility symbols have higher estimated volatility."""
        normal_vol = agent._estimate_volatility("JNJ")
        high_vol = agent._estimate_volatility("TSLA")

        assert high_vol >= normal_vol * 1.2  # Should be significantly higher

    def test_high_skill_more_accurate(self):
        """"Test that high skill produces more consistent estimates."""
        high_skill_agent = RiskManager(
            agent_id="high", initial_capital=10000.0, skill_level=0.9
        )
        low_skill_agent = RiskManager(
            agent_id="low", initial_capital=10000.0, skill_level=0.3
        )

        # Multiple estimates
        high_skill_vols = [high_skill_agent._estimate_volatility("AAPL") for _ in range(10)]
        low_skill_vols = [low_skill_agent._estimate_volatility("AAPL") for _ in range(10)]

        # High skill should have lower variance
        high_variance = max(high_skill_vols) - min(high_skill_vols)
        low_variance = max(low_skill_vols) - min(low_skill_vols)

        assert high_variance <= low_variance * 1.5  # Allow some randomness


class TestVaRCalculation:
    """"Test VaR calculation methods."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_var_95_less_than_var_99(self, agent):
        """"Test that 95% VaR is less than 99% VaR."""
        volatility = 0.25

        var_95 = agent._calculate_var(volatility, confidence=0.95, position_size=10000.0)
        var_99 = agent._calculate_var(volatility, confidence=0.99, position_size=10000.0)

        assert var_95 < var_99

    def test_var_increases_with_volatility(self, agent):
        """"Test that VaR increases with higher volatility."""
        var_low = agent._calculate_var(0.20, position_size=10000.0)
        var_high = agent._calculate_var(0.40, position_size=10000.0)

        assert var_high > var_low

    def test_var_increases_with_position_size(self, agent):
        """"Test that VaR increases with position size."""
        volatility = 0.25

        var_small = agent._calculate_var(volatility, position_size=1000.0)
        var_large = agent._calculate_var(volatility, position_size=5000.0)

        assert var_large > var_small


class TestRiskLevelClassification:
    """"Test risk level classification."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_low_risk(self, agent):
        """"Test low risk classification."""
        assert agent._classify_risk_level(0.10) == "low"
        assert agent._classify_risk_level(0.19) == "low"

    def test_medium_risk(self, agent):
        """"Test medium risk classification."""
        assert agent._classify_risk_level(0.20) == "medium"
        assert agent._classify_risk_level(0.34) == "medium"

    def test_high_risk(self, agent):
        """"Test high risk classification."""
        assert agent._classify_risk_level(0.35) == "high"
        assert agent._classify_risk_level(0.49) == "high"

    def test_extreme_risk(self, agent):
        """"Test extreme risk classification."""
        assert agent._classify_risk_level(0.50) == "extreme"
        assert agent._classify_risk_level(0.80) == "extreme"


class TestConcentrationRisk:
    """"Test concentration risk calculation."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_equal_weights_low_concentration(self, agent):
        """"Test equal weights have lower concentration risk."""
        positions = {"A": 1000.0, "B": 1000.0, "C": 1000.0, "D": 1000.0}

        concentration = agent._calculate_concentration_risk(positions)

        # Equal weights should have concentration = 1/n = 0.25
        assert abs(concentration - 0.25) < 0.01

    def test_single_position_max_concentration(self, agent):
        """"Test single position has maximum concentration."""
        positions = {"A": 1000.0}

        concentration = agent._calculate_concentration_risk(positions)

        assert concentration == 1.0

    def test_empty_positions_zero_concentration(self, agent):
        """"Test empty positions have zero concentration."""
        concentration = agent._calculate_concentration_risk({})

        assert concentration == 0.0


class TestRiskHistory:
    """"Test risk history methods."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_get_risk_history_returns_copy(self, agent):
        """"Test that get_risk_history returns a copy."""
        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))

        history = agent.get_risk_history()
        history.append(None)  # Modify the copy

        # Original should be unchanged
        assert len(agent._risk_history) == 1

    def test_get_latest_risk_assessment(self, agent):
        """"Test getting latest assessment for a symbol."""
        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))
        asyncio.run(agent.assess_risk("GOOGL", position_size=2000.0))
        asyncio.run(agent.assess_risk("AAPL", position_size=1500.0))  # Second AAPL

        latest = agent.get_latest_risk_assessment("AAPL")

        assert latest is not None
        assert latest.symbol == "AAPL"
        assert latest.var_95 > 0

    def test_get_latest_returns_none_if_no_assessment(self, agent):
        """"Test getting latest when no assessment exists."""
        latest = agent.get_latest_risk_assessment("UNKNOWN")

        assert latest is None

    def test_clear_history(self, agent):
        """"Test clearing risk history."""
        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))
        agent.assess_portfolio_risk("portfolio-1", {"AAPL": 1000.0})

        agent.clear_history()

        assert len(agent._risk_history) == 0
        assert len(agent._portfolio_risk_history) == 0


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

    def test_decision_cost_is_20_cents(self):
        """"Test that decision cost is $0.20."""
        agent = RiskManager(agent_id="test", initial_capital=10000.0)

        assert agent.decision_cost == 0.20

    def test_multiple_assessments_deduct_each_time(self):
        """"Test that each assessment deducts $0.20."""
        agent = RiskManager(agent_id="test", initial_capital=10000.0)
        initial_balance = agent.balance

        asyncio.run(agent.assess_risk("AAPL", position_size=1000.0))
        asyncio.run(agent.assess_risk("GOOGL", position_size=1000.0))
        asyncio.run(agent.assess_risk("MSFT", position_size=1000.0))

        expected_deduction = 0.20 * 3
        assert agent.balance == initial_balance - expected_deduction


class TestPositionRecommendation:
    """"Test position size recommendation."""

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return RiskManager(agent_id="test", initial_capital=10000.0)

    def test_recommendation_with_low_volatility(self, agent):
        """"Test recommendation with low volatility."""
        # Use higher max_risk_per_trade to test volatility factor without risk cap
        agent.max_risk_per_trade = 0.50  # 50% to avoid risk-based capping
        recommendation = agent._calculate_position_recommendation(
            requested_size=5000.0, volatility=0.15, portfolio=None
        )

        # Low volatility should allow full or near-full position
        assert recommendation > 4000.0

    def test_recommendation_with_high_volatility(self, agent):
        """"Test recommendation with high volatility."""
        recommendation = agent._calculate_position_recommendation(
            requested_size=5000.0, volatility=0.50, portfolio=None
        )

        # High volatility should significantly reduce position
        assert recommendation < 2500.0

    def test_high_skill_increases_recommendation(self):
        """"Test that high skill increases recommendation."""
        high_skill_agent = RiskManager(
            agent_id="high", initial_capital=10000.0, skill_level=0.9
        )
        low_skill_agent = RiskManager(
            agent_id="low", initial_capital=10000.0, skill_level=0.3
        )

        high_rec = high_skill_agent._calculate_position_recommendation(
            requested_size=5000.0, volatility=0.30, portfolio=None
        )
        low_rec = low_skill_agent._calculate_position_recommendation(
            requested_size=5000.0, volatility=0.30, portfolio=None
        )

        assert high_rec >= low_rec


class TestRiskReport:
    """"Test RiskReport dataclass."""

    def test_risk_report_creation(self):
        """"Test creating a RiskReport."""
        report = RiskReport(
            symbol="AAPL",
            risk_level="medium",
            volatility=0.25,
            var_95=100.0,
            var_99=150.0,
            max_drawdown_estimate=-0.35,
            position_size_recommendation=2000.0,
            warnings=["High volatility"],
        )

        assert report.symbol == "AAPL"
        assert report.risk_level == "medium"
        assert report.volatility == 0.25
        assert report.var_95 == 100.0
        assert report.var_99 == 150.0
        assert report.max_drawdown_estimate == -0.35
        assert report.position_size_recommendation == 2000.0
        assert report.warnings == ["High volatility"]

    def test_risk_report_default_warnings(self):
        """"Test RiskReport with default empty warnings."""
        report = RiskReport(
            symbol="AAPL",
            risk_level="low",
            volatility=0.15,
            var_95=50.0,
            var_99=75.0,
            max_drawdown_estimate=-0.20,
            position_size_recommendation=5000.0,
        )

        assert report.warnings == []


class TestPortfolioRiskMetrics:
    """"Test PortfolioRiskMetrics dataclass."""

    def test_portfolio_risk_metrics_creation(self):
        """"Test creating PortfolioRiskMetrics."""
        metrics = PortfolioRiskMetrics(
            portfolio_id="portfolio-1",
            total_exposure=10000.0,
            concentration_risk=0.35,
            correlation_risk=0.25,
            portfolio_var_95=500.0,
            portfolio_var_99=750.0,
            sector_exposure={"tech": 0.4, "healthcare": 0.3},
            risk_adjusted_return=0.15,
        )

        assert metrics.portfolio_id == "portfolio-1"
        assert metrics.total_exposure == 10000.0
        assert metrics.concentration_risk == 0.35
        assert metrics.correlation_risk == 0.25
        assert metrics.portfolio_var_95 == 500.0
        assert metrics.portfolio_var_99 == 750.0
        assert metrics.sector_exposure == {"tech": 0.4, "healthcare": 0.3}
        assert metrics.risk_adjusted_return == 0.15
