"""Unit tests for FundamentalAnalyst agent.

This module tests the FundamentalAnalyst class including fundamental analysis,
valuation metrics calculation, and report generation.
"""

import asyncio
from unittest.mock import patch

import pytest

from openclaw.agents.base import ActivityType
from openclaw.agents.fundamental_analyst import (
    FundamentalAnalyst,
    FundamentalReport,
    ValuationRecommendation,
)
from openclaw.core.economy import SurvivalStatus


class TestFundamentalAnalystInitialization:
    """Test FundamentalAnalyst initialization."""

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

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

    def test_custom_initialization(self):
        """Test agent with custom parameters."""
        agent = FundamentalAnalyst(
            agent_id="fundamental-2",
            initial_capital=5000.0,
            skill_level=0.8,
        )

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

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

        agent = FundamentalAnalyst(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 FundamentalAnalyst(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.PAPER_TRADE]

    def test_struggling_status_when_cannot_afford(self, agent):
        """Test struggling status when cannot afford analysis."""
        agent.economic_tracker.balance = 0.20  # Very low, can't afford $0.10 with safety buffer

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

        assert result in [ActivityType.LEARN, ActivityType.PAPER_TRADE, ActivityType.REST]

    def test_healthy_status_performs_analysis(self, agent):
        """Test healthy status with sufficient funds performs analysis."""
        agent.economic_tracker.balance = 12000.0  # STABLE status (>110%)

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

        assert result == ActivityType.ANALYZE


class TestAnalyzeFundamentals:
    """Test analyze_fundamentals method."""

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

    def test_returns_fundamental_report(self, agent):
        """Test that analyze_fundamentals returns FundamentalReport."""
        result = asyncio.run(agent.analyze_fundamentals("AAPL"))

        assert isinstance(result, FundamentalReport)
        assert result.symbol == "AAPL"

    def test_deducts_decision_cost(self, agent):
        """Test that analysis deducts $0.10 decision cost."""
        initial_balance = agent.balance

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

        assert agent.balance == initial_balance - 0.10

    def test_valuation_metrics_present(self, agent):
        """Test that valuation metrics are present in report."""
        result = asyncio.run(agent.analyze_fundamentals("AAPL"))

        assert "pe_ratio" in result.valuation_metrics
        assert "pb_ratio" in result.valuation_metrics
        assert "market_cap" in result.valuation_metrics

    def test_profitability_metrics_present(self, agent):
        """Test that profitability metrics are present in report."""
        result = asyncio.run(agent.analyze_fundamentals("TSLA"))

        assert "roe" in result.profitability_metrics
        assert "roa" in result.profitability_metrics
        assert "profit_margin" in result.profitability_metrics

    def test_growth_metrics_present(self, agent):
        """Test that growth metrics are present in report."""
        result = asyncio.run(agent.analyze_fundamentals("MSFT"))

        assert "revenue_growth" in result.growth_metrics
        assert "earnings_growth" in result.growth_metrics

    def test_overall_score_range(self, agent):
        """Test that overall score is in valid range (0-100)."""
        result = asyncio.run(agent.analyze_fundamentals("GOOGL"))

        assert 0 <= result.overall_score <= 100

    def test_recommendation_valid(self, agent):
        """Test that recommendation is valid."""
        result = asyncio.run(agent.analyze_fundamentals("AMZN"))

        assert result.recommendation in [
            ValuationRecommendation.UNDERVALUED,
            ValuationRecommendation.FAIR,
            ValuationRecommendation.OVERVALUED,
        ]

    def test_stores_last_report(self, agent):
        """Test that analysis stores the last report."""
        assert agent.get_last_report() is None

        asyncio.run(agent.analyze_fundamentals("NVDA"))

        assert agent.get_last_report() is not None
        assert agent.get_last_report().symbol == "NVDA"

    def test_timestamp_auto_generated(self, agent):
        """Test that timestamp is auto-generated."""
        result = asyncio.run(agent.analyze_fundamentals("META"))

        assert result.timestamp != ""
        assert "T" in result.timestamp  # ISO format


class TestCalculateValuationMetrics:
    """Test _calculate_valuation_metrics method."""

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

    def test_calculates_pe_ratio(self, agent):
        """Test PE ratio calculation."""
        data = {"price": 100.0, "eps": 5.0, "book_value": 50.0, "revenue": 1e9}

        metrics = agent._calculate_valuation_metrics(data)

        assert metrics["pe_ratio"] == 20.0  # 100 / 5

    def test_calculates_pb_ratio(self, agent):
        """Test PB ratio calculation."""
        data = {"price": 100.0, "eps": 5.0, "book_value": 50.0, "revenue": 1e9}

        metrics = agent._calculate_valuation_metrics(data)

        assert metrics["pb_ratio"] == 2.0  # 100 / 50

    def test_handles_zero_values(self, agent):
        """Test handling of zero values."""
        data = {"price": 100.0, "eps": 0, "book_value": 0, "revenue": 1e9}

        metrics = agent._calculate_valuation_metrics(data)

        assert metrics["pe_ratio"] == float("inf")
        assert metrics["pb_ratio"] == float("inf")


class TestCalculateProfitabilityMetrics:
    """Test _calculate_profitability_metrics method."""

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

    def test_calculates_roe(self, agent):
        """Test ROE calculation."""
        data = {
            "net_income": 1e9,
            "shareholders_equity": 5e9,
            "total_assets": 10e9,
            "revenue": 10e9,
        }

        metrics = agent._calculate_profitability_metrics(data)

        assert metrics["roe"] == 0.2  # 1e9 / 5e9

    def test_calculates_roa(self, agent):
        """Test ROA calculation."""
        data = {
            "net_income": 1e9,
            "shareholders_equity": 5e9,
            "total_assets": 10e9,
            "revenue": 10e9,
        }

        metrics = agent._calculate_profitability_metrics(data)

        assert metrics["roa"] == 0.1  # 1e9 / 10e9

    def test_calculates_profit_margin(self, agent):
        """Test profit margin calculation."""
        data = {
            "net_income": 2e9,
            "shareholders_equity": 5e9,
            "total_assets": 10e9,
            "revenue": 10e9,
        }

        metrics = agent._calculate_profitability_metrics(data)

        assert metrics["profit_margin"] == 0.2  # 2e9 / 10e9


class TestCalculateGrowthMetrics:
    """Test _calculate_growth_metrics method."""

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

    def test_extracts_growth_rates(self, agent):
        """Test growth rate extraction."""
        data = {"revenue_growth": 0.15, "earnings_growth": 0.25}

        metrics = agent._calculate_growth_metrics(data)

        assert metrics["revenue_growth"] == 0.15
        assert metrics["earnings_growth"] == 0.25


class TestCalculateOverallScore:
    """Test _calculate_overall_score method."""

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

    def test_high_score_for_good_metrics(self, agent):
        """Test high score for strong fundamentals."""
        valuation = {"pe_ratio": 10, "pb_ratio": 1.0}
        profitability = {"roe": 0.20, "roa": 0.10, "profit_margin": 0.25}
        growth = {"revenue_growth": 0.15, "earnings_growth": 0.20}

        score = agent._calculate_overall_score(valuation, profitability, growth)

        assert score >= 70  # Should be high score

    def test_low_score_for_poor_metrics(self, agent):
        """Test low score for weak fundamentals."""
        valuation = {"pe_ratio": 50, "pb_ratio": 5.0}
        profitability = {"roe": 0.03, "roa": 0.02, "profit_margin": 0.03}
        growth = {"revenue_growth": -0.05, "earnings_growth": -0.10}

        score = agent._calculate_overall_score(valuation, profitability, growth)

        assert score <= 50  # Should be low score

    def test_score_clamped_to_100(self, agent):
        """Test score doesn't exceed 100."""
        valuation = {"pe_ratio": 5, "pb_ratio": 0.5}
        profitability = {"roe": 0.50, "roa": 0.30, "profit_margin": 0.50}
        growth = {"revenue_growth": 0.50, "earnings_growth": 0.50}

        score = agent._calculate_overall_score(valuation, profitability, growth)

        assert score <= 100

    def test_score_clamped_to_0(self, agent):
        """Test score doesn't go below 0."""
        valuation = {"pe_ratio": 100, "pb_ratio": 10.0}
        profitability = {"roe": -0.10, "roa": -0.05, "profit_margin": -0.05}
        growth = {"revenue_growth": -0.30, "earnings_growth": -0.30}

        score = agent._calculate_overall_score(valuation, profitability, growth)

        assert score >= 0


class TestGenerateRecommendation:
    """Test _generate_recommendation method."""

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

    def test_undervalued_recommendation(self, agent):
        """Test undervalued recommendation for high score and low PE."""
        valuation = {"pe_ratio": 10}

        rec = agent._generate_recommendation(75, valuation)

        assert rec == ValuationRecommendation.UNDERVALUED

    def test_overvalued_recommendation(self, agent):
        """Test overvalued recommendation for low score or very high PE."""
        valuation = {"pe_ratio": 50}

        rec = agent._generate_recommendation(35, valuation)

        assert rec == ValuationRecommendation.OVERVALUED

    def test_fair_recommendation(self, agent):
        """Test fair recommendation for neutral conditions."""
        valuation = {"pe_ratio": 20}

        rec = agent._generate_recommendation(55, valuation)

        assert rec == ValuationRecommendation.FAIR


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

    @pytest.fixture
    def agent(self):
        """Create a test agent."""
        return FundamentalAnalyst(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 "overall_score" in result
        assert "recommendation" in result
        assert "valuation_metrics" in result
        assert "profitability_metrics" in result
        assert "growth_metrics" 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

    def test_analyze_includes_cost_in_result(self, agent):
        """Test that analyze result includes cost."""
        result = asyncio.run(agent.analyze("AAPL"))

        assert result["cost"] == 0.10


class TestGetLastReport:
    """Test get_last_report method."""

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

    def test_returns_none_when_no_analysis(self, agent):
        """Test returns None when no analysis performed."""
        assert agent.get_last_report() is None

    def test_returns_report_after_analysis(self, agent):
        """Test returns report after analysis."""
        asyncio.run(agent.analyze_fundamentals("AAPL"))

        report = agent.get_last_report()

        assert report is not None
        assert report.symbol == "AAPL"


class TestGetReportHistory:
    """Test get_report_history method."""

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

    def test_returns_empty_list_when_no_analysis(self, agent):
        """Test returns empty list when no analysis performed."""
        assert agent.get_report_history() == []

    def test_returns_list_with_report_after_analysis(self, agent):
        """Test returns list with report after analysis."""
        asyncio.run(agent.analyze_fundamentals("AAPL"))

        history = agent.get_report_history()

        assert len(history) == 1
        assert history[0].symbol == "AAPL"


class TestFundamentalReport:
    """Test FundamentalReport dataclass."""

    def test_report_creation(self):
        """Test creating a FundamentalReport."""
        report = FundamentalReport(
            symbol="AAPL",
            valuation_metrics={"pe_ratio": 20.0},
            profitability_metrics={"roe": 0.15},
            growth_metrics={"revenue_growth": 0.10},
            overall_score=75.0,
            recommendation=ValuationRecommendation.UNDERVALUED,
        )

        assert report.symbol == "AAPL"
        assert report.overall_score == 75.0
        assert report.recommendation == ValuationRecommendation.UNDERVALUED

    def test_timestamp_auto_generated(self):
        """Test that timestamp is auto-generated."""
        report = FundamentalReport(
            symbol="AAPL",
            valuation_metrics={},
            profitability_metrics={},
            growth_metrics={},
            overall_score=75.0,
            recommendation=ValuationRecommendation.FAIR,
        )

        assert report.timestamp != ""
        assert "T" in report.timestamp  # ISO format

    def test_custom_timestamp(self):
        """Test FundamentalReport with custom timestamp."""
        report = FundamentalReport(
            symbol="AAPL",
            valuation_metrics={},
            profitability_metrics={},
            growth_metrics={},
            overall_score=75.0,
            recommendation=ValuationRecommendation.FAIR,
            timestamp="2024-01-01T00:00:00",
        )

        assert report.timestamp == "2024-01-01T00:00:00"


class TestValuationRecommendation:
    """Test ValuationRecommendation enum."""

    def test_recommendation_values(self):
        """Test recommendation enum values."""
        assert ValuationRecommendation.UNDERVALUED == "undervalued"
        assert ValuationRecommendation.FAIR == "fair"
        assert ValuationRecommendation.OVERVALUED == "overvalued"
