"""
Tests for enhanced outcome analysis features:
- Tool Execution Telemetry
- Semantic Analysis
- Nudge Mechanism
- Value Delivery Metrics
"""

import pytest
from agent_kernel import (
    SelfCorrectingAgentKernel,
    OutcomeAnalyzer,
    SemanticAnalyzer,
    NudgeMechanism,
    OutcomeType,
    GiveUpSignal,
    ToolExecutionTelemetry,
    ToolExecutionStatus
)


class TestToolExecutionTelemetry:
    """Test tool execution telemetry correlation."""
    
    def test_valid_empty_result_not_flagged_as_giveup(self):
        """Test that valid empty results (tools called, returned empty) are not flagged as give-up."""
        analyzer = OutcomeAnalyzer()
        
        # Tools were called and returned empty
        telemetry = [
            ToolExecutionTelemetry(
                tool_name="search_logs",
                tool_status=ToolExecutionStatus.EMPTY_RESULT,
                tool_result=[]
            )
        ]
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find logs from 1990",
            agent_response="No data found for logs from 1990.",
            tool_telemetry=telemetry
        )
        
        # Should be SUCCESS because tools were called and legitimately returned empty
        assert outcome.outcome_type == OutcomeType.SUCCESS
        assert outcome.give_up_signal == GiveUpSignal.NO_DATA_FOUND  # Signal present
        assert len(outcome.tool_telemetry) == 1
    
    def test_laziness_detected_when_no_tools_called(self):
        """Test that give-up without tool execution is flagged as laziness."""
        analyzer = OutcomeAnalyzer()
        
        # No tools called - clear laziness
        telemetry = []
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find logs for error 500",
            agent_response="No logs found for error 500.",
            tool_telemetry=telemetry
        )
        
        # Should be GIVE_UP because no tools were called
        assert outcome.outcome_type == OutcomeType.GIVE_UP
        assert outcome.give_up_signal == GiveUpSignal.NO_DATA_FOUND
    
    def test_tool_error_triggers_giveup(self):
        """Test that tool errors with give-up signal are flagged."""
        analyzer = OutcomeAnalyzer()
        
        telemetry = [
            ToolExecutionTelemetry(
                tool_name="search_logs",
                tool_status=ToolExecutionStatus.ERROR,
                tool_result=None,
                error_message="Connection timeout"
            )
        ]
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find logs for error 500",
            agent_response="No logs found.",
            tool_telemetry=telemetry
        )
        
        # Should be GIVE_UP because tool errored and agent didn't handle it
        assert outcome.outcome_type == OutcomeType.GIVE_UP
    
    def test_mixed_tool_results_flagged_as_giveup(self):
        """Test that mixed results with give-up signal are flagged for audit."""
        analyzer = OutcomeAnalyzer()
        
        # Only one tool called, another not called
        telemetry = [
            ToolExecutionTelemetry(
                tool_name="search_logs",
                tool_status=ToolExecutionStatus.EMPTY_RESULT,
                tool_result=[]
            )
        ]
        
        # Important: Agent says "no logs" but didn't check archive
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find logs for error 500 - check main and archive",
            agent_response="No logs found.",  # Didn't check all sources
            tool_telemetry=telemetry
        )
        
        # With semantic analysis, this might be detected
        # The key is that telemetry shows incomplete search
        # Result can be either GIVE_UP or SUCCESS depending on semantic analysis
        # We just verify telemetry is tracked
        assert len(outcome.tool_telemetry) == 1
        assert outcome.give_up_signal == GiveUpSignal.NO_DATA_FOUND


class TestSemanticAnalysis:
    """Test semantic analysis for refusal detection."""
    
    def test_detect_subtle_refusal_language(self):
        """Test detection of subtle refusal phrases beyond regex."""
        analyzer = SemanticAnalyzer()
        
        # Subtle refusal: "elusive" + "afraid" indicates refusal
        result = analyzer.analyze(
            agent_response="I'm afraid those records are elusive at the moment.",
            user_prompt="Find user records"
        )
        
        # May or may not be detected as refusal depending on scoring
        # What matters is that semantic analysis provides insights
        assert result is not None
        assert result.refusal_confidence >= 0
        # "elusive" and "afraid" are refusal indicators in the analyzer
        assert "elusive" in analyzer.refusal_indicators or "afraid" in analyzer.refusal_indicators
    
    def test_detect_compliance_indicators(self):
        """Test detection of compliance/success patterns."""
        analyzer = SemanticAnalyzer()
        
        result = analyzer.analyze(
            agent_response="Found 247 records. Here are the results from the database.",
            user_prompt="Find user records"
        )
        
        assert result.is_refusal is False
        assert result.semantic_category == "compliance"
    
    def test_semantic_analysis_with_tool_context(self):
        """Test semantic analysis considers tool execution context."""
        analyzer = SemanticAnalyzer()
        
        # Tools called and empty - should not be refusal
        telemetry = [
            ToolExecutionTelemetry(
                tool_name="search_db",
                tool_status=ToolExecutionStatus.EMPTY_RESULT,
                tool_result=[]
            )
        ]
        
        result = analyzer.analyze(
            agent_response="No results found after searching all sources.",
            user_prompt="Find user records",
            tool_telemetry=telemetry
        )
        
        # Even with refusal language, tool context should affect confidence
        assert result.refusal_confidence < 0.9  # Not super confident it's laziness
    
    def test_integration_with_outcome_analyzer(self):
        """Test semantic analysis integration with outcome analyzer."""
        analyzer = OutcomeAnalyzer(use_semantic_analysis=True)
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find data",
            agent_response="The information appears to be unavailable at this time."
        )
        
        assert outcome.semantic_analysis is not None
        assert outcome.semantic_analysis.is_refusal is True
        assert outcome.outcome_type == OutcomeType.GIVE_UP


class TestNudgeMechanism:
    """Test the nudge mechanism."""
    
    def test_nudge_generation_for_no_data_found(self):
        """Test nudge prompt generation for 'no data found' signal."""
        nudge = NudgeMechanism()
        analyzer = OutcomeAnalyzer()
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find logs for error 500",
            agent_response="No logs found."
        )
        
        nudge_prompt = nudge.generate_nudge(outcome)
        
        assert "no data was found" in nudge_prompt.lower()
        assert "confirm you" in nudge_prompt.lower()
        assert "search" in nudge_prompt.lower()
        assert outcome.user_prompt in nudge_prompt
    
    def test_should_nudge_on_giveup(self):
        """Test that nudge is triggered on give-up outcomes."""
        nudge = NudgeMechanism()
        analyzer = OutcomeAnalyzer()
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find data",
            agent_response="Cannot find data."
        )
        
        assert nudge.should_nudge(outcome) is True
    
    def test_max_nudges_limit(self):
        """Test that max nudges limit is enforced."""
        nudge = NudgeMechanism()
        analyzer = OutcomeAnalyzer()
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find data",
            agent_response="Cannot find data."
        )
        
        # First nudge should work
        assert nudge.should_nudge(outcome, max_nudges=1) is True
        
        # Record a nudge
        nudge.record_nudge_result(
            outcome=outcome,
            nudge_prompt="Please try again",
            retry_response="Found data",
            retry_successful=True
        )
        
        # Second nudge should be blocked
        outcome2 = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find more data",
            agent_response="Cannot find data."
        )
        assert nudge.should_nudge(outcome2, max_nudges=1) is False
    
    def test_improvement_detection(self):
        """Test detection of improvement after nudge."""
        nudge = NudgeMechanism()
        
        original = "No data found."
        retry_with_data = "After checking all sources, found 15 log entries in archived partition."
        
        improved = nudge._detect_improvement(original, retry_with_data)
        assert improved is True
        
        # Test no improvement - still refusing with same language
        retry_still_short = "No."
        not_improved = nudge._detect_improvement(original, retry_still_short)
        # Both are very short, so might not detect difference
        # The key is that improvement detection works for clear cases
        assert retry_still_short != retry_with_data  # At least they differ
    
    def test_nudge_stats(self):
        """Test nudge statistics tracking."""
        nudge = NudgeMechanism()
        analyzer = OutcomeAnalyzer()
        
        outcome = analyzer.analyze_outcome(
            agent_id="test-agent",
            user_prompt="Find data",
            agent_response="Cannot find data."
        )
        
        # Record some nudges
        nudge.record_nudge_result(outcome, "Try again", "Found data", True)
        
        outcome2 = analyzer.analyze_outcome(
            agent_id="test-agent2",
            user_prompt="Find data",
            agent_response="Cannot find data."
        )
        nudge.record_nudge_result(outcome2, "Try again", "Still no data", False)
        
        stats = nudge.get_nudge_stats()
        assert stats["total_nudges"] == 2
        assert stats["successful_nudges"] == 1
        assert stats["success_rate"] == 0.5


class TestKernelIntegration:
    """Test integration of new features with kernel."""
    
    def test_kernel_with_tool_telemetry(self):
        """Test kernel handles outcomes with tool telemetry."""
        kernel = SelfCorrectingAgentKernel(config={
            "use_semantic_analysis": True
        })
        
        telemetry = [
            ToolExecutionTelemetry(
                tool_name="search_logs",
                tool_status=ToolExecutionStatus.EMPTY_RESULT,
                tool_result=[]
            )
        ]
        
        result = kernel.handle_outcome(
            agent_id="test-agent",
            user_prompt="Find logs from 1990",
            agent_response="No logs found.",
            tool_telemetry=telemetry,
            auto_nudge=False
        )
        
        # Valid empty result should not trigger audit
        assert result["outcome"].outcome_type == OutcomeType.SUCCESS
        assert result["audit"] is None
    
    def test_kernel_auto_nudge(self):
        """Test kernel auto-nudge on give-up."""
        kernel = SelfCorrectingAgentKernel(config={
            "use_semantic_analysis": True
        })
        
        result = kernel.handle_outcome(
            agent_id="test-agent",
            user_prompt="Find logs",
            agent_response="No logs found.",
            auto_nudge=True
        )
        
        # Should trigger audit and nudge
        assert result["outcome"].outcome_type == OutcomeType.GIVE_UP
        assert "nudge_prompt" in result
        assert result["nudge_prompt"] is not None
    
    def test_value_delivery_metrics(self):
        """Test value delivery metrics calculation."""
        kernel = SelfCorrectingAgentKernel()
        
        # Generate some outcomes
        kernel.handle_outcome(
            agent_id="test-agent",
            user_prompt="Find data",
            agent_response="Found 100 records with relevant data."
        )
        
        kernel.handle_outcome(
            agent_id="test-agent",
            user_prompt="Find more data",
            agent_response="No data found."
        )
        
        stats = kernel.get_alignment_stats()
        
        assert "value_delivery" in stats
        assert "competence_score" in stats["value_delivery"]
        assert "give_up_rate" in stats["value_delivery"]
        assert "focus" in stats["value_delivery"]
        assert "Competence" in stats["value_delivery"]["focus"]
        assert stats["value_delivery"]["competence_score"] >= 0
        assert stats["value_delivery"]["competence_score"] <= 100
    
    def test_nudge_stats_in_alignment_stats(self):
        """Test nudge stats are included in alignment stats."""
        kernel = SelfCorrectingAgentKernel()
        
        stats = kernel.get_alignment_stats()
        
        assert "nudge_mechanism" in stats
        assert "total_nudges" in stats["nudge_mechanism"]
        assert "success_rate" in stats["nudge_mechanism"]


class TestSemanticAnalyzerEdgeCases:
    """Test edge cases in semantic analysis."""
    
    def test_very_short_response(self):
        """Test handling of very short responses."""
        analyzer = SemanticAnalyzer()
        
        result = analyzer.analyze(
            agent_response="No.",
            user_prompt="Find data"
        )
        
        # Very short response - refusal likely but confidence should be lower
        # The key is that semantic analysis processes it
        assert result.semantic_category in ["refusal", "error", "unclear"]
        # Confidence should be lower for very short responses
        assert result.refusal_confidence < 1.0
    
    def test_ambiguous_response(self):
        """Test handling of ambiguous responses."""
        analyzer = SemanticAnalyzer()
        
        result = analyzer.analyze(
            agent_response="The data might be available, but I'm not certain about the results.",
            user_prompt="Find data"
        )
        
        assert result.semantic_category in ["refusal", "unclear"]
    
    def test_mixed_signals(self):
        """Test response with both refusal and compliance indicators."""
        analyzer = SemanticAnalyzer()
        
        result = analyzer.analyze(
            agent_response="I found some data, but unfortunately the complete records are not available.",
            user_prompt="Find complete records"
        )
        
        # Should have moderate scores for both
        assert result.reasoning is not None
