"""
Unit tests for Professional Logger module.

This test module provides comprehensive coverage for storm_logger.professional_logger,
including StormProfessionalLogger class and related functions.
"""

from unittest.mock import Mock, patch

import pytest

from storm_logger.config import StormLoggerConfig
from storm_logger.core.logger import StormLogger
from storm_logger.professional_logger import (
    StormProfessionalLogger,
    get_professional_logger,
    get_professional_test_logger,
    professional_logger,
)


class TestStormProfessionalLogger:
    """Test StormProfessionalLogger class."""

    @pytest.fixture
    def mock_config(self):
        """Create mock configuration."""
        config = Mock(spec=StormLoggerConfig)
        config.name = "test_logger"
        config.log_level = "INFO"
        return config

    @pytest.fixture
    def mock_console(self):
        """Create mock console."""
        console = Mock()
        console.print_test_progress = Mock()
        console.print_analysis = Mock()
        console.print_error = Mock()
        console.print_metrics = Mock()
        console.print_solution = Mock()
        console.print_django_spellbook_tip = Mock()
        console.print_midas_optimization = Mock()
        console.print_storm_animation_insight = Mock()
        console.print_business_recommendation = Mock()
        console.print_session_summary = Mock()
        console.print_header = Mock()
        console.print_business_context = Mock()
        return console

    @pytest.fixture
    def mock_formatter(self):
        """Create mock formatter."""
        formatter = Mock()
        formatter.format_error_message = Mock(
            return_value={
                "type": "TestError",
                "message": "Test error message",
                "guidance": "Fix the test",
            }
        )
        formatter.format_metric_message = Mock(
            return_value={"name": "test_metric", "value": 100, "target": 150, "status": "optimal"}
        )
        formatter.format_success_message = Mock(return_value="Success message")
        formatter.format_django_spellbook_tip = Mock(return_value="Django tip")
        formatter.format_midas_optimization = Mock(return_value="MIDAS optimization")
        formatter.format_test_message = Mock(
            return_value={"details": "Test details", "recommendation": "Test recommendation"}
        )
        formatter.format_business_recommendation = Mock(
            return_value=["Recommendation 1", "Recommendation 2"]
        )
        return formatter

    @pytest.fixture
    def logger(self, mock_config, mock_console, mock_formatter):
        """Create StormProfessionalLogger instance with mocks."""
        with patch(
            "storm_logger.professional_logger.get_storm_console", return_value=mock_console
        ), patch(
            "storm_logger.professional_logger.StormMessageFormatter", return_value=mock_formatter
        ):
            return StormProfessionalLogger("test", mock_config)

    def test_init(self, mock_config, mock_console):
        """Test logger initialization."""
        with patch("storm_logger.professional_logger.get_storm_console", return_value=mock_console):
            logger = StormProfessionalLogger("test", mock_config)
            assert logger.name == "test"
            assert logger.console == mock_console
            assert logger._show_console is True

    def test_format_message_with_console(self, logger, mock_console):
        """Test message formatting with console output."""
        with patch.object(logger, "_display_professional") as mock_display:
            logger._format_message("Test message", "INFO")
            mock_display.assert_called_once_with("Test message", "INFO", None)

    def test_format_message_without_console(self, logger):
        """Test message formatting with console disabled."""
        logger._show_console = False
        with patch.object(logger, "_display_professional") as mock_display:
            logger._format_message("Test message", "INFO")
            mock_display.assert_not_called()

    def test_display_professional_debug(self, logger, mock_console):
        """Test professional display for DEBUG level."""
        logger._display_professional("Debug message", "DEBUG")
        # Debug messages are ignored (pass statement)
        mock_console.print_analysis.assert_not_called()

    def test_display_professional_info_test_context(self, logger, mock_console):
        """Test professional display for INFO level with test context."""
        # Test passed
        logger._display_professional("Test passed", "INFO", context="test")
        mock_console.print_test_progress.assert_called_with("Test passed", "passed")

        # Test failed
        logger._display_professional("Test failed", "INFO", context="test")
        mock_console.print_test_progress.assert_called_with("Test failed", "failed")

        # Test running
        logger._display_professional("Running test", "INFO", context="test")
        mock_console.print_test_progress.assert_called_with("Running test", "running")

    def test_display_professional_info_general(self, logger, mock_console):
        """Test professional display for INFO level general context."""
        logger._display_professional("General info", "INFO")
        mock_console.print_analysis.assert_called_with("SYSTEM OPERATION", "General info")

    def test_display_professional_warning(self, logger, mock_console):
        """Test professional display for WARNING level."""
        logger._display_professional("Warning message", "WARNING")
        mock_console.print_analysis.assert_called_with("BUSINESS ATTENTION", "Warning message")

    def test_display_professional_error_with_exc_info(self, logger, mock_console, mock_formatter):
        """Test professional display for ERROR level with exception info."""
        exc_info = ("TestError", "Test error", None)
        logger._display_professional("Error message", "ERROR", exc_info=exc_info)
        mock_formatter.format_error_message.assert_called_with("TestError", "Test error")
        mock_console.print_error.assert_called_with(
            "SYSTEM ERROR", mock_formatter.format_error_message.return_value
        )

    def test_display_professional_error_without_exc_info(self, logger, mock_console):
        """Test professional display for ERROR level without exception info."""
        logger._display_professional("Error message", "ERROR")
        mock_console.print_error.assert_called_with("SYSTEM ERROR", "Error message")

    def test_display_professional_critical(self, logger, mock_console):
        """Test professional display for CRITICAL level."""
        logger._display_professional("Critical error", "CRITICAL")
        mock_console.print_error.assert_called_with(
            "CRITICAL FAILURE", "Critical error", "Contact Storm Development support immediately"
        )

    def test_business_metric(self, logger, mock_console, mock_formatter):
        """Test business metric logging."""
        with patch.object(logger, "info") as mock_info:
            logger.business_metric("response_time", 100, 150)

            mock_formatter.format_metric_message.assert_called_with("response_time", 100, 150)
            mock_console.print_metrics.assert_called_with(
                {"test_metric": {"value": 100, "target": 150, "status": True}}
            )
            mock_info.assert_called_with("Business metric: response_time=100")

    def test_django_operation_success(self, logger, mock_console, mock_formatter):
        """Test Django operation logging with success."""
        with patch.object(logger, "success") as mock_success:
            logger.django_operation("create", "User", success=True)

            mock_formatter.format_success_message.assert_called_with("create", {"model": "User"})
            mock_console.print_solution.assert_called_with(
                "Success message", "Django-Spellbook pattern applied successfully"
            )
            mock_console.print_django_spellbook_tip.assert_called_with("Django tip")
            mock_success.assert_called_with("Success message")

    def test_django_operation_failure(self, logger, mock_console):
        """Test Django operation logging with failure."""
        with patch.object(logger, "error") as mock_error:
            logger.django_operation("update", "Product", success=False)

            mock_console.print_error.assert_called_with(
                "DJANGO OPERATION FAILURE",
                "Django operation failed: update for Product",
                "Review Django-Spellbook best practices",
            )
            mock_error.assert_called_with("Django operation failed: update for Product")

    def test_midas_optimization_valid_metrics(self, logger, mock_console, mock_formatter):
        """Test MIDAS optimization with valid metrics."""
        with patch.object(logger, "info") as mock_info:
            logger.midas_optimization("cache", 200, 50)

            # 75% improvement
            mock_console.print_midas_optimization.assert_called_with(
                "cache optimized: 75.0% performance gain"
            )
            mock_console.print_analysis.assert_called_with(
                "MIDAS OPTIMIZATION", "MIDAS optimization"
            )
            mock_info.assert_called_with("MIDAS optimization: cache improved by 75.0%")

    def test_midas_optimization_zero_before(self, logger, mock_console):
        """Test MIDAS optimization with zero before metric."""
        with patch.object(logger, "info"):
            logger.midas_optimization("startup", 0, 100)

            # 100% improvement when before is 0 and after > 0
            mock_console.print_midas_optimization.assert_called_with(
                "startup optimized: 100.0% performance gain"
            )

    def test_midas_optimization_invalid_metrics(self, logger, mock_console):
        """Test MIDAS optimization with invalid metrics."""
        with patch.object(logger, "info"):
            logger.midas_optimization("invalid", "not_a_number", "also_not")

            # 0% improvement on error
            mock_console.print_midas_optimization.assert_called_with(
                "invalid optimized: 0.0% performance gain"
            )

    def test_storm_animation_event_basic(self, logger, mock_console):
        """Test Storm animation event logging."""
        with patch.object(logger, "info") as mock_info:
            details = {"param1": "value1", "param2": "value2"}
            logger.storm_animation_event("hover", details)

            mock_console.print_storm_animation_insight.assert_called_with(
                "Animation event 'hover' triggered with 2 parameters"
            )
            mock_info.assert_called_with("Storm Animation: hover", **details)

    def test_storm_animation_event_with_conversion(self, logger, mock_console):
        """Test Storm animation event with conversion data."""
        with patch.object(logger, "info"):
            details = {"conversion": 25, "clicks": 100}
            logger.storm_animation_event("cta_pulse", details)

            mock_console.print_business_recommendation.assert_called_with(
                "Animation Conversion Impact",
                [
                    "Event 'cta_pulse' shows 25% conversion rate",
                    "Consider A/B testing this animation pattern",
                    "Track user engagement in Storm Dashboard",
                ],
            )

    def test_test_pass(self, logger, mock_console, mock_formatter):
        """Test test_pass method."""
        # Mock the parent class method to avoid level issues
        with patch.object(StormLogger, "test_pass"):
            logger.test_pass("test_example")

            mock_formatter.format_test_message.assert_called_with("test_example", "passed")
            mock_console.print_test_progress.assert_called_with("test_example", "passed", None)

    def test_test_fail(self, logger, mock_console, mock_formatter):
        """Test test_fail method."""
        # Mock the parent class method to avoid level issues
        with patch.object(StormLogger, "test_fail"):
            logger.test_fail("test_example", "Assertion failed")

            mock_formatter.format_test_message.assert_called_with(
                "test_example", "failed", "Assertion failed"
            )
            mock_console.print_test_progress.assert_called_with("test_example", "failed", None)
            mock_console.print_business_recommendation.assert_called_with(
                "Quality Assurance Action Required", ["Test recommendation"]
            )

    def test_session_summary_high_success_rate(self, logger, mock_console):
        """Test session summary with high success rate."""
        with patch.object(logger, "info") as mock_info:
            logger.session_summary(100, 95, 5, 120.5)

            mock_console.print_session_summary.assert_called_with(100, 95, 5, 120.5)
            mock_info.assert_called_with("Test session complete: 100 tests, 95 passed, 5 failed")
            # No recommendations for 95% success rate
            assert mock_console.print_business_recommendation.call_count == 0

    def test_session_summary_low_success_rate(self, logger, mock_console, mock_formatter):
        """Test session summary with low success rate."""
        with patch.object(logger, "info"):
            logger.session_summary(100, 70, 30, 120.5)

            mock_console.print_business_recommendation.assert_called()
            # Verify recommendations were requested
            mock_formatter.format_business_recommendation.assert_called_with(
                "quality assurance", "performance"
            )

    def test_session_summary_zero_tests(self, logger, mock_console):
        """Test session summary with zero tests."""
        with patch.object(logger, "info"):
            logger.session_summary(0, 0, 0, 0.0)

            # Should not raise ZeroDivisionError
            mock_console.print_session_summary.assert_called_with(0, 0, 0, 0.0)

    def test_print_header(self, logger, mock_console):
        """Test print header."""
        with patch.object(logger, "info") as mock_info:
            logger.print_header("Main Title", "Subtitle")

            mock_console.print_header.assert_called_with("Main Title", "Subtitle")
            mock_info.assert_called_with("=== Main Title === - Subtitle")

    def test_print_header_no_subtitle(self, logger, mock_console):
        """Test print header without subtitle."""
        with patch.object(logger, "info") as mock_info:
            logger.print_header("Main Title")

            mock_console.print_header.assert_called_with("Main Title", None)
            mock_info.assert_called_with("=== Main Title ===")

    def test_teaching_moment_django(self, logger, mock_console):
        """Test teaching moment with Django context."""
        # Mock the parent class method
        with patch.object(StormLogger, "teaching_moment"):
            logger.teaching_moment("Django best practices")

            mock_console.print_business_context.assert_called_with(
                "Django best practices", "django"
            )

    def test_teaching_moment_midas(self, logger, mock_console):
        """Test teaching moment with MIDAS context."""
        # Mock the parent class method
        with patch.object(StormLogger, "teaching_moment"):
            logger.teaching_moment("MIDAS optimization techniques")

            mock_console.print_business_context.assert_called_with(
                "MIDAS optimization techniques", "midas"
            )

    def test_teaching_moment_animation(self, logger, mock_console):
        """Test teaching moment with animation context."""
        # Mock the parent class method
        with patch.object(StormLogger, "teaching_moment"):
            logger.teaching_moment("Animation performance tips")

            mock_console.print_business_context.assert_called_with(
                "Animation performance tips", "animation"
            )

    def test_helpful_tip_django(self, logger, mock_console, mock_formatter):
        """Test helpful tip with Django context."""
        # Mock the parent class method
        with patch.object(StormLogger, "helpful_tip"):
            logger.helpful_tip("Django ORM optimization")

            mock_formatter.format_django_spellbook_tip.assert_called_with("Django ORM optimization")
            mock_console.print_django_spellbook_tip.assert_called_with("Django tip")

    def test_helpful_tip_midas(self, logger, mock_console, mock_formatter):
        """Test helpful tip with MIDAS context."""
        # Mock the parent class method
        with patch.object(StormLogger, "helpful_tip"):
            logger.helpful_tip("MIDAS caching strategy")

            mock_formatter.format_midas_optimization.assert_called_with("MIDAS caching strategy")
            mock_console.print_midas_optimization.assert_called_with("MIDAS optimization")

    def test_helpful_tip_performance(self, logger, mock_console, mock_formatter):
        """Test helpful tip with performance context."""
        # Mock the parent class method
        with patch.object(StormLogger, "helpful_tip"):
            logger.helpful_tip("Performance monitoring setup")

            mock_formatter.format_midas_optimization.assert_called_with(
                "Performance monitoring setup"
            )
            mock_console.print_midas_optimization.assert_called_with("MIDAS optimization")

    def test_helpful_tip_general(self, logger, mock_console):
        """Test helpful tip with general context."""
        # Mock the parent class method
        with patch.object(StormLogger, "helpful_tip"):
            logger.helpful_tip("General programming tip")

            mock_console.print_analysis.assert_called_with(
                "PROFESSIONAL TIP", "General programming tip"
            )


class TestModuleFunctions:
    """Test module-level functions."""

    def test_get_professional_logger_with_name(self):
        """Test get_professional_logger with explicit name."""
        with patch("storm_logger.professional_logger.StormProfessionalLogger") as mock_class:
            get_professional_logger("test.module")
            mock_class.assert_called_once_with("test.module", None)

    def test_get_professional_logger_with_config(self):
        """Test get_professional_logger with config."""
        config = Mock(spec=StormLoggerConfig)
        with patch("storm_logger.professional_logger.StormProfessionalLogger") as mock_class:
            get_professional_logger("test.module", config)
            mock_class.assert_called_once_with("test.module", config)

    def test_get_professional_logger_without_name(self):
        """Test get_professional_logger without name (uses inspect)."""
        with patch("storm_logger.professional_logger.StormProfessionalLogger") as mock_class:
            get_professional_logger()
            # Should use inspect to get the name
            call_args = mock_class.call_args[0]
            assert (
                call_args[0] == "tests.unit.test_professional_logger"
            )  # Full module name from test

    def test_get_professional_test_logger(self):
        """Test get_professional_test_logger."""
        with patch("storm_logger.professional_logger.get_professional_logger") as mock_get:
            mock_logger = Mock()
            mock_get.return_value = mock_logger

            result = get_professional_test_logger("test.logger")

            mock_get.assert_called_once_with("test.logger")
            assert mock_logger.test_mode is True
            assert result == mock_logger

    def test_get_professional_test_logger_no_name(self):
        """Test get_professional_test_logger without name."""
        with patch("storm_logger.professional_logger.get_professional_logger") as mock_get:
            mock_logger = Mock()
            mock_get.return_value = mock_logger

            get_professional_test_logger()

            mock_get.assert_called_once_with(None)
            assert mock_logger.test_mode is True

    def test_professional_logger_global_instance(self):
        """Test global professional_logger instance."""
        assert professional_logger is not None
        assert isinstance(professional_logger, StormProfessionalLogger)
        assert professional_logger.name == "storm.professional"


class TestIntegrationScenarios:
    """Test integration scenarios for professional logger."""

    @pytest.fixture
    def full_logger(self):
        """Create a full logger with minimal mocking."""
        from storm_logger.config import LogLevel, StormLoggerConfig

        config = StormLoggerConfig(log_level=LogLevel.INFO)
        with patch("storm_logger.professional_logger.get_storm_console") as mock_console:
            mock_console.return_value = Mock()
            return StormProfessionalLogger("integration_test", config)

    def test_error_handling_flow(self, full_logger):
        """Test complete error handling flow."""
        # Mock the formatter's format_error_message to return expected structure
        with patch.object(full_logger.formatter, "format_error_message") as mock_format:
            mock_format.return_value = {
                "type": "ValueError",
                "message": "Test error occurred",
                "guidance": "Check input validation",
            }

            def raise_error():
                raise ValueError("Test error")

            try:
                raise_error()
            except Exception:
                import sys

                with patch.object(full_logger, "error"):
                    full_logger._display_professional(
                        "Error occurred", "ERROR", exc_info=sys.exc_info()
                    )
                    # Verify error logging was attempted
                    assert full_logger.console.print_error.called

    def test_metric_tracking_flow(self, full_logger):
        """Test complete metric tracking flow."""
        with patch.object(full_logger, "info") as mock_info:
            # Track multiple metrics
            full_logger.business_metric("api_calls", 1000, 1500)
            full_logger.business_metric("response_time", 0.250, "< 0.3s")
            full_logger.business_metric("error_rate", 0.02, "< 0.05")

            # Verify all metrics were logged
            assert mock_info.call_count == 3

    def test_test_session_flow(self, full_logger):
        """Test complete test session flow."""
        with patch.object(full_logger, "info"), patch.object(
            full_logger, "test_pass", wraps=full_logger.test_pass
        ), patch.object(full_logger, "test_fail", wraps=full_logger.test_fail):
            # Run test session
            full_logger.print_header("Test Session", "Unit Tests")
            full_logger.test_pass("test_feature_1")
            full_logger.test_pass("test_feature_2")
            full_logger.test_fail("test_feature_3", "Assertion failed")
            full_logger.session_summary(3, 2, 1, 5.5)

            # Verify complete flow
            assert full_logger.console.print_header.called
            assert full_logger.console.print_test_progress.call_count >= 3
            assert full_logger.console.print_session_summary.called


# Module exports test
def test_module_exports():
    """Test that all expected exports are available."""
    from storm_logger import professional_logger as module

    assert hasattr(module, "StormProfessionalLogger")
    assert hasattr(module, "get_professional_logger")
    assert hasattr(module, "get_professional_test_logger")
    assert hasattr(module, "professional_logger")

    # Check __all__
    assert "StormProfessionalLogger" in module.__all__
    assert "get_professional_logger" in module.__all__
    assert "get_professional_test_logger" in module.__all__
    assert "professional_logger" in module.__all__
