"""
Comprehensive unit tests for base_implementations module.
Tests all concrete implementations to achieve 100% coverage.
"""

import pytest
import logging
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from typing import Any, Dict

from storm_logger.core.base_implementations import (
    DefaultLoggerFactory, DefaultDependencyChecker, DefaultFormatterFactory,
    DefaultConfigurationBuilder, DefaultDirectoryManager
)
from storm_logger.config import StormLoggerConfig, LogLevel


class TestDefaultLoggerFactory:
    """Test DefaultLoggerFactory implementation."""
    
    def test_get_logger(self):
        """Test getting a logger instance."""
        factory = DefaultLoggerFactory()
        
        with patch('logging.getLogger') as mock_get_logger:
            mock_logger = Mock()
            mock_get_logger.return_value = mock_logger
            
            result = factory.get_logger("test_logger")
            
            assert result is mock_logger
            mock_get_logger.assert_called_once_with("test_logger")
    
    def test_set_level(self):
        """Test setting logger level."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        
        factory.set_level(mock_logger, logging.DEBUG)
        mock_logger.setLevel.assert_called_once_with(logging.DEBUG)
    
    def test_get_level(self):
        """Test getting logger level."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        mock_logger.level = logging.WARNING
        
        result = factory.get_level(mock_logger)
        assert result == logging.WARNING
    
    def test_is_enabled_for(self):
        """Test checking if level is enabled."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        mock_logger.isEnabledFor.return_value = True
        
        result = factory.is_enabled_for(mock_logger, logging.INFO)
        
        assert result is True
        mock_logger.isEnabledFor.assert_called_once_with(logging.INFO)
    
    def test_log_simple(self):
        """Test simple logging without special kwargs."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        
        factory.log(mock_logger, logging.INFO, "Test message", custom="data")
        
        mock_logger.log.assert_called_once_with(
            logging.INFO, 
            "Test message", 
            extra={'custom': 'data'}
        )
    
    def test_log_with_extra(self):
        """Test logging with extra dict."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        
        factory.log(mock_logger, logging.DEBUG, "Debug msg", 
                   extra={'request_id': '123'}, user="admin")
        
        mock_logger.log.assert_called_once_with(
            logging.DEBUG,
            "Debug msg",
            extra={'request_id': '123', 'user': 'admin'}
        )
    
    def test_log_error_with_exc_info(self):
        """Test error logging with exception info."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        
        factory.log(mock_logger, logging.ERROR, "Error occurred", 
                   exc_info=True, code=500)
        
        mock_logger.log.assert_called_once_with(
            logging.ERROR,
            "Error occurred",
            exc_info=True,
            extra={'code': 500}
        )
    
    def test_log_critical_with_exc_info(self):
        """Test critical logging with exception info."""
        factory = DefaultLoggerFactory()
        mock_logger = Mock()
        
        factory.log(mock_logger, logging.CRITICAL, "System failure", 
                   exc_info=False, severity="high")
        
        mock_logger.log.assert_called_once_with(
            logging.CRITICAL,
            "System failure",
            exc_info=False,
            extra={'severity': 'high'}
        )


class TestDefaultDependencyChecker:
    """Test DefaultDependencyChecker implementation."""
    
    def test_is_available_true(self):
        """Test when module is available."""
        checker = DefaultDependencyChecker()
        
        with patch('builtins.__import__') as mock_import:
            result = checker.is_available("existing_module")
            
            assert result is True
            mock_import.assert_called_once_with("existing_module")
    
    def test_is_available_false(self):
        """Test when module is not available."""
        checker = DefaultDependencyChecker()
        
        with patch('builtins.__import__', side_effect=ImportError("Not found")):
            result = checker.is_available("missing_module")
            
            assert result is False


class TestDefaultFormatterFactory:
    """Test DefaultFormatterFactory implementation."""
    
    @pytest.fixture
    def mock_checker_with_modules(self):
        """Mock checker with specific modules available."""
        checker = Mock()
        checker.is_available.side_effect = lambda m: m in ['colorlog', 'pythonjsonlogger']
        return checker
    
    @pytest.fixture
    def mock_checker_no_modules(self):
        """Mock checker with no modules available."""
        checker = Mock()
        checker.is_available.return_value = False
        return checker
    
    def test_init_with_default_checker(self):
        """Test initialization with default dependency checker."""
        factory = DefaultFormatterFactory()
        assert factory.dependency_checker is not None
    
    def test_init_with_custom_checker(self, mock_checker_with_modules):
        """Test initialization with custom dependency checker."""
        factory = DefaultFormatterFactory(mock_checker_with_modules)
        assert factory.dependency_checker is mock_checker_with_modules
    
    def test_create_colored_formatter_with_colorlog(self, mock_checker_with_modules):
        """Test colored formatter when colorlog is available."""
        factory = DefaultFormatterFactory(mock_checker_with_modules)
        result = factory.create_colored_formatter()
        
        assert result['()'] == 'colorlog.ColoredFormatter'
        assert 'log_colors' in result
        assert result['log_colors']['DEBUG'] == 'blue'
        assert result['log_colors']['INFO'] == 'green'
        assert result['log_colors']['WARNING'] == 'yellow'
        assert result['log_colors']['ERROR'] == 'red'
        assert result['log_colors']['CRITICAL'] == 'red,bg_white'
        assert '{log_color}' in result['format']
    
    def test_create_colored_formatter_without_colorlog(self, mock_checker_no_modules):
        """Test colored formatter fallback to standard."""
        factory = DefaultFormatterFactory(mock_checker_no_modules)
        result = factory.create_colored_formatter()
        
        assert '()' not in result
        assert result['format'] == '[{levelname}] {asctime} | {name} | {message}'
        assert result['style'] == '{'
        assert result['datefmt'] == '%Y-%m-%d %H:%M:%S'
    
    def test_create_json_formatter_with_module(self, mock_checker_with_modules):
        """Test JSON formatter when pythonjsonlogger is available."""
        factory = DefaultFormatterFactory(mock_checker_with_modules)
        result = factory.create_json_formatter()
        
        assert result['()'] == 'pythonjsonlogger.jsonlogger.JsonFormatter'
        assert '%(asctime)s' in result['format']
        assert '%(levelname)s' in result['format']
        assert result['datefmt'] == '%Y-%m-%d %H:%M:%S'
    
    def test_create_json_formatter_without_module(self, mock_checker_no_modules):
        """Test JSON formatter fallback to verbose."""
        factory = DefaultFormatterFactory(mock_checker_no_modules)
        result = factory.create_json_formatter()
        
        assert '()' not in result
        assert 'Process:' in result['format']
        assert 'Thread:' in result['format']
        assert '{process:d}' in result['format']
        assert result['style'] == '{'
    
    def test_create_standard_formatter(self):
        """Test standard formatter creation."""
        factory = DefaultFormatterFactory()
        result = factory.create_standard_formatter()
        
        assert result['format'] == '[{levelname}] {asctime} | {name} | {message}'
        assert result['style'] == '{'
        assert result['datefmt'] == '%Y-%m-%d %H:%M:%S'


class TestDefaultConfigurationBuilder:
    """Test DefaultConfigurationBuilder implementation."""
    
    @pytest.fixture
    def mock_config(self):
        """Mock configuration object."""
        config = Mock(spec=StormLoggerConfig)
        config.log_directory = Path("/test/logs")
        config.log_level = Mock(value="INFO")
        config.console_colors = True
        config.enable_file_logging = True
        config.max_file_size = 10485760  # 10MB
        config.backup_count = 5
        config.debug_mode = False
        return config
    
    @pytest.fixture
    def mock_formatter_factory(self):
        """Mock formatter factory."""
        factory = Mock()
        factory.create_standard_formatter.return_value = {'standard': 'formatter'}
        factory.create_json_formatter.return_value = {'json': 'formatter'}
        factory.create_colored_formatter.return_value = {'colored': 'formatter'}
        return factory
    
    def test_init_with_default_factory(self):
        """Test initialization with default formatter factory."""
        builder = DefaultConfigurationBuilder()
        assert builder.formatter_factory is not None
    
    def test_init_with_custom_factory(self, mock_formatter_factory):
        """Test initialization with custom formatter factory."""
        builder = DefaultConfigurationBuilder(mock_formatter_factory)
        assert builder.formatter_factory is mock_formatter_factory
    
    def test_build_base_config_minimal(self):
        """Test building minimal configuration."""
        config = Mock(spec=StormLoggerConfig)
        config.log_directory = Path("/logs")
        config.log_level = Mock(value="WARNING")
        config.console_colors = False
        config.enable_file_logging = False
        config.debug_mode = False
        
        builder = DefaultConfigurationBuilder()
        result = builder.build_base_config(config)
        
        assert result['version'] == 1
        assert result['disable_existing_loggers'] is False
        assert 'formatters' in result
        assert 'handlers' in result
        assert 'loggers' in result
        
        # Should have console handlers only
        assert 'console' in result['handlers']
        assert 'console_error' in result['handlers']
        assert 'app_file' not in result['handlers']
        
        # Console should use standard formatter
        assert result['handlers']['console']['formatter'] == 'standard'
    
    def test_build_base_config_with_colors(self, mock_config):
        """Test configuration with colored console."""
        mock_config.console_colors = True
        mock_config.enable_file_logging = False
        
        builder = DefaultConfigurationBuilder()
        result = builder.build_base_config(mock_config)
        
        assert result['handlers']['console']['formatter'] == 'colored'
        assert result['handlers']['console_error']['formatter'] == 'colored'
    
    def test_build_base_config_with_file_logging(self, mock_config):
        """Test configuration with file logging enabled."""
        builder = DefaultConfigurationBuilder()
        result = builder.build_base_config(mock_config)
        
        # Should have file handlers
        assert 'app_file' in result['handlers']
        assert 'error_file' in result['handlers']
        
        # Check app file handler
        app_handler = result['handlers']['app_file']
        assert app_handler['class'] == 'logging.handlers.RotatingFileHandler'
        assert app_handler['filename'] == str(Path("/test/logs/app/storm_app.log"))
        assert app_handler['maxBytes'] == 10485760
        assert app_handler['backupCount'] == 5
        assert app_handler['formatter'] == 'verbose'
        assert app_handler['level'] == 'INFO'
        
        # Check error file handler
        error_handler = result['handlers']['error_file']
        assert error_handler['maxBytes'] == 5242880  # Half of max_file_size
        assert error_handler['backupCount'] == 10  # Double backup_count
        assert error_handler['level'] == 'ERROR'
        
        # Loggers should include file handlers
        assert 'app_file' in result['loggers']['']['handlers']
        assert 'error_file' in result['loggers']['storm']['handlers']
    
    def test_build_base_config_with_debug_mode(self, mock_config):
        """Test configuration with debug mode enabled."""
        mock_config.debug_mode = True
        
        builder = DefaultConfigurationBuilder()
        result = builder.build_base_config(mock_config)
        
        # Should have debug handler
        assert 'debug_file' in result['handlers']
        
        debug_handler = result['handlers']['debug_file']
        assert debug_handler['level'] == 'DEBUG'
        assert debug_handler['filename'] == str(Path("/test/logs/app/debug.log"))
        assert debug_handler['maxBytes'] == 20971520  # Double max_file_size
        assert debug_handler['backupCount'] == 2
        
        # All loggers should have debug handler
        for logger_config in result['loggers'].values():
            assert 'debug_file' in logger_config['handlers']
    
    def test_build_base_config_loggers(self, mock_config):
        """Test logger configuration."""
        builder = DefaultConfigurationBuilder()
        result = builder.build_base_config(mock_config)
        
        # Should have root and storm loggers
        assert '' in result['loggers']  # Root logger
        assert 'storm' in result['loggers']
        
        # Both should have same configuration
        root_logger = result['loggers']['']
        storm_logger = result['loggers']['storm']
        
        assert root_logger['level'] == 'INFO'
        assert storm_logger['level'] == 'INFO'
        assert root_logger['propagate'] is False
        assert storm_logger['propagate'] is False
    
    def test_add_debug_handler(self, mock_config):
        """Test adding debug handler to existing config."""
        logging_config = {
            'handlers': {},
            'loggers': {
                '': {'handlers': ['console']},
                'storm': {'handlers': ['console']}
            }
        }
        
        builder = DefaultConfigurationBuilder()
        builder.add_debug_handler(logging_config, mock_config)
        
        # Should add debug handler
        assert 'debug_file' in logging_config['handlers']
        
        # Should add to all loggers
        assert 'debug_file' in logging_config['loggers']['']['handlers']
        assert 'debug_file' in logging_config['loggers']['storm']['handlers']
    
    def test_add_debug_handler_idempotent(self, mock_config):
        """Test that debug handler is not added twice."""
        logging_config = {
            'handlers': {},
            'loggers': {
                '': {'handlers': ['console', 'debug_file']},
                'storm': {'handlers': ['console']}
            }
        }
        
        builder = DefaultConfigurationBuilder()
        builder.add_debug_handler(logging_config, mock_config)
        
        # Should not duplicate in first logger
        assert logging_config['loggers']['']['handlers'].count('debug_file') == 1
        
        # Should add to second logger
        assert 'debug_file' in logging_config['loggers']['storm']['handlers']


class TestDefaultDirectoryManager:
    """Test DefaultDirectoryManager implementation."""
    
    def test_create_directories(self):
        """Test creating directories from config."""
        manager = DefaultDirectoryManager()
        mock_config = Mock()
        
        manager.create_directories(mock_config)
        
        mock_config.create_log_directories.assert_called_once()
    
    def test_ensure_directory_exists(self):
        """Test ensuring a directory exists."""
        manager = DefaultDirectoryManager()
        mock_path = Mock(spec=Path)
        
        manager.ensure_directory_exists(mock_path)
        
        mock_path.mkdir.assert_called_once_with(parents=True, exist_ok=True)


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