"""Tests for the logging module."""
from __future__ import annotations

import logging as stdlib_logging
import os
import tempfile
import shutil
from pathlib import Path

from spafw37 import logging
from spafw37.logging import (
    TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL,
    log, log_trace, log_debug, log_info, log_warning, log_error,
    set_current_scope, set_log_dir,
    set_file_level, set_console_level, set_silent_mode,
    set_no_logging_mode, set_suppress_errors, set_scope_log_level,
    get_scope_log_level, LOGGING_PARAMS, apply_logging_config,
    LOG_VERBOSE_PARAM,
    LOG_TRACE_PARAM,
    LOG_TRACE_CONSOLE_PARAM,
    LOG_SILENT_PARAM,
    LOG_NO_LOGGING_PARAM,
    LOG_NO_FILE_LOGGING_PARAM,
    LOG_SUPPRESS_ERRORS_PARAM,
    LOG_DIR_PARAM,
    LOG_LEVEL_PARAM,
    LOG_PHASE_LOG_LEVEL_PARAM,
)
from spafw37 import config_func as config, param
from spafw37.constants.param import PARAM_NAME


def setup_function():
    """Reset logging module state before each test.
    
    This ensures each test starts with a clean logging configuration,
    preventing test interference from shared handler state.
    """
    # Remove any existing handlers from stdlib logger if it exists
    stdlib_logger = stdlib_logging.getLogger('spafw37')
    for handler in stdlib_logger.handlers[:]:
        stdlib_logger.removeHandler(handler)
        handler.close()
    
    # Reset logging module state
    logging._logger = None
    logging._file_handler = None
    logging._console_handler = None
    logging._error_handler = None
    logging._current_scope = None
    logging._suppress_errors = False
    logging._scope_log_levels = {}
    logging._log_dir = logging._DEFAULT_LOG_DIR
    
    # Reset params module state
    param._PARAMS = []
    
    # Reset config module state (both modules)
    from spafw37 import config as config_module
    config_module._config = {}
    config._persistent_config = {}


def test_trace_level_exists():
    """Test that TRACE level is defined."""
    assert TRACE == 5
    assert TRACE < DEBUG


def test_log_function_basic():
    """Test basic log function."""
    # Should not raise
    log(_level=INFO, _message="Test message")
    log_info(_message="Test info message")


def test_log_with_scope():
    """Test logging with scope."""
    log(_level=INFO, _scope="test-scope", _message="Test message with scope")


def test_set_app_name():
    """Test setting application name."""
    config.set_app_name("test-app")
    log_info(_message="Test message")


def test_set_current_scope():
    """Test setting current scope."""
    set_current_scope("setup-scope")
    log_info(_message="Test message in scope")
    set_current_scope(None)


def test_set_log_dir():
    """Test setting log directory."""
    with tempfile.TemporaryDirectory() as temp_dir:
        test_log_dir = os.path.join(temp_dir, "test_logs")
        set_log_dir(test_log_dir)
        log_info(_message="Test message")
        
        # Check that log directory was created
        assert os.path.exists(test_log_dir)
        
        # Check that a log file was created
        log_files = list(Path(test_log_dir).glob("log-*.log"))
        assert len(log_files) > 0


def test_log_levels():
    """Test all log level convenience functions."""
    log_trace(_message="Trace message")
    log_debug(_message="Debug message")
    log_info(_message="Info message")
    log_warning(_message="Warning message")
    log_error(_message="Error message")


def test_set_file_level():
    """Test setting file log level."""
    set_file_level(WARNING)
    log_info(_message="This should not appear in file")


def test_set_console_level():
    """Test setting console log level."""
    set_console_level(ERROR)
    log_info(_message="This should not appear on console")


def test_set_silent_mode():
    """Test silent mode."""
    set_silent_mode(True)
    log_info(_message="This should be silent")
    set_silent_mode(False)


def test_set_no_logging_mode():
    """Test no-logging mode."""
    set_no_logging_mode(True)
    log_info(_message="This should not log anywhere")
    set_no_logging_mode(False)


def test_set_suppress_errors():
    """Test error suppression."""
    set_suppress_errors(True)
    log_error(_message="This error should be suppressed")
    set_suppress_errors(False)


def test_scope_log_level():
    """Test scope-specific log levels."""
    set_scope_log_level("test-scope", WARNING)
    assert get_scope_log_level("test-scope") == WARNING
    
    # Info level should not log for this scope
    log(_level=INFO, _scope="test-scope", _message="Should be filtered")
    
    # Warning level should log
    log(_level=WARNING, _scope="test-scope", _message="Should appear")


def test_logging_params_defined():
    """Test that all logging params are defined."""
    assert len(LOGGING_PARAMS) == 10
    
    param_names = {p['name'] for p in LOGGING_PARAMS}
    expected_params = {
        LOG_VERBOSE_PARAM,
        LOG_TRACE_PARAM,
        LOG_TRACE_CONSOLE_PARAM,
        LOG_SILENT_PARAM,
        LOG_NO_LOGGING_PARAM,
        LOG_NO_FILE_LOGGING_PARAM,
        LOG_SUPPRESS_ERRORS_PARAM,
        LOG_DIR_PARAM,
        LOG_LEVEL_PARAM,
        LOG_PHASE_LOG_LEVEL_PARAM,
    }
    assert param_names == expected_params


def test_apply_logging_config_verbose():
    """Test applying verbose logging config."""
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Set verbose flag
    verbose_param = param.get_param_by_name(LOG_VERBOSE_PARAM)
    config.set_config_value(verbose_param, True)
    
    # Apply config
    apply_logging_config()
    
    # Verify console level is DEBUG
    # (We can't directly check the level, but we can verify it doesn't raise)


def test_apply_logging_config_trace():
    """Test applying trace logging config."""
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Set trace flag
    trace_param = param.get_param_by_name(LOG_TRACE_PARAM)
    config.set_config_value(trace_param, True)
    
    # Apply config
    apply_logging_config()


def test_apply_logging_config_log_dir():
    """Test applying log directory config."""
    with tempfile.TemporaryDirectory() as temp_dir:
        # Add logging params
        param.add_params(LOGGING_PARAMS)
        
        # Set log dir
        test_log_dir = os.path.join(temp_dir, "config_logs")
        log_dir_param = param.get_param_by_name(LOG_DIR_PARAM)
        config.set_config_value(log_dir_param, test_log_dir)
        
        # Apply config
        apply_logging_config()
        
        # Log a message
        log_info(_message="Test message in configured dir")
        
        # Verify log dir was created
        assert os.path.exists(test_log_dir)


def test_apply_logging_config_scope_log_level():
    """Test applying scope-specific log level config."""
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Set scope log level (using phase-log-level param for backward compatibility)
    phase_param = param.get_param_by_name(LOG_PHASE_LOG_LEVEL_PARAM)
    config.set_config_value(phase_param, ["setup", "WARNING", "execution", "DEBUG"])
    
    # Apply config
    apply_logging_config()
    
    # Verify scope levels were set
    assert get_scope_log_level("setup") == WARNING
    assert get_scope_log_level("execution") == DEBUG


def test_log_file_naming_pattern():
    """Test that log files follow the naming pattern log-{yyyy-MM-dd-hh.mm.ss}.log."""
    with tempfile.TemporaryDirectory() as temp_dir:
        set_log_dir(temp_dir)
        log_info(_message="Test message for filename check")
        
        log_files = list(Path(temp_dir).glob("log-*.log"))
        assert len(log_files) > 0
        
        # Check filename pattern (should match log-YYYY-MM-DD-HH.MM.SS.log)
        log_file = log_files[0]
        name = log_file.name
        assert name.startswith("log-")
        assert name.endswith(".log")
        assert len(name.split("-")) >= 4  # log, YYYY, MM, DD-HH.MM.SS.log


def test_logging_params_persistence():
    """Test that logging params have correct persistence settings."""
    # Verbose should not persist
    verbose_param = [p for p in LOGGING_PARAMS if p['name'] == LOG_VERBOSE_PARAM][0]
    assert verbose_param['persistence'] == 'never'
    
    # Log dir should persist
    log_dir_param = [p for p in LOGGING_PARAMS if p['name'] == LOG_DIR_PARAM][0]
    assert log_dir_param['persistence'] == 'always'


def test_switch_list_conflicts():
    """Test that mutually exclusive params are defined in switch lists."""
    silent_param = [p for p in LOGGING_PARAMS if p['name'] == LOG_SILENT_PARAM][0]
    assert LOG_VERBOSE_PARAM in silent_param['switch-list']
    assert LOG_NO_LOGGING_PARAM in silent_param['switch-list']
    
    no_logging_param = [p for p in LOGGING_PARAMS if p['name'] == LOG_NO_LOGGING_PARAM][0]
    assert LOG_SILENT_PARAM in no_logging_param['switch-list']


def test_command_execution_logging():
    """Test that command execution produces INFO level logs."""
    from spafw37 import command
    from spafw37.constants.command import (
        COMMAND_NAME, COMMAND_ACTION, COMMAND_PHASE
    )
    from spafw37.constants.phase import PHASE_EXECUTION
    
    # Set up test environment
    with tempfile.TemporaryDirectory() as temp_dir:
        set_log_dir(temp_dir)
        
        # Reset command module state completely
        command._commands = {}
        command._finished_commands = []
        command._phases = {PHASE_EXECUTION: []}
        command._phases_completed = []
        command._command_queue = []
        
        # Create a test command
        test_executed = {'value': False}
        
        def test_action():
            test_executed['value'] = True
        
        test_cmd = {
            COMMAND_NAME: 'test-cmd',
            COMMAND_ACTION: test_action,
            COMMAND_PHASE: PHASE_EXECUTION,
        }
        
        # Register and queue command
        command.add_command(test_cmd)
        command.queue_command('test-cmd')
        
        # Execute command queue
        command.run_command_queue()
        
        # Verify command was executed
        assert test_executed['value'] is True
        
        # Verify log file contains command execution logs
        log_files = list(Path(temp_dir).glob("log-*.log"))
        assert len(log_files) > 0
        
        log_content = log_files[0].read_text()
        assert 'Starting command: test-cmd' in log_content
        assert 'Completed command: test-cmd' in log_content


def test_param_setting_logging():
    """Test that param value setting produces DEBUG level logs."""
    from spafw37.constants.param import PARAM_CONFIG_NAME, PARAM_TYPE
    
    with tempfile.TemporaryDirectory() as temp_dir:
        set_log_dir(temp_dir)
        set_file_level(DEBUG)
        
        # Create a test param
        test_param = {
            PARAM_NAME: 'test-param-log',
            PARAM_CONFIG_NAME: 'test-param-log',
            PARAM_TYPE: 'text',
        }
        
        # Set param value
        config.set_config_value(test_param, 'test-value')
        
        # Verify log file contains param setting logs
        log_files = list(Path(temp_dir).glob("log-*.log"))
        assert len(log_files) > 0
        
        log_content = log_files[0].read_text()
        assert "Set param 'test-param-log' = test-value" in log_content


def test_should_log_to_console_with_no_logging():
    """Test console logging is disabled when LOG_NO_LOGGING_PARAM is set.
    
    When the no-logging parameter is enabled, console logging should be
    disabled regardless of other settings. This validates the logging
    suppression logic (line 211).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-logging param
    no_logging_param = param.get_param_by_name(LOG_NO_LOGGING_PARAM)
    config.set_config_value(no_logging_param, True)
    
    # Verify console logging is disabled
    assert logging._should_log_to_console() is False


def test_should_log_to_console_in_silent_mode():
    """Test console logging is disabled in silent mode.
    
    When silent mode is enabled, console logging should be disabled.
    This validates the silent mode check (line 213).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set silent param
    silent_param = param.get_param_by_name(LOG_SILENT_PARAM)
    config.set_config_value(silent_param, True)
    
    # Verify console logging is disabled
    assert logging._should_log_to_console() is False


def test_should_log_to_file_with_no_logging():
    """Test file logging is disabled when LOG_NO_LOGGING_PARAM is set.
    
    When the no-logging parameter is enabled, file logging should be
    disabled. This validates the logging suppression logic (line 220).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-logging param
    no_logging_param = param.get_param_by_name(LOG_NO_LOGGING_PARAM)
    config.set_config_value(no_logging_param, True)
    
    # Verify file logging is disabled
    assert logging._should_log_to_file() is False


def test_should_log_to_file_with_no_file_logging():
    """Test file logging is disabled when LOG_NO_FILE_LOGGING_PARAM is set.
    
    When the no-file-logging parameter is enabled, file logging should be
    disabled even if console logging is still active. This validates the
    file-specific suppression logic (line 222).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-file-logging param
    no_file_logging_param = param.get_param_by_name(LOG_NO_FILE_LOGGING_PARAM)
    config.set_config_value(no_file_logging_param, True)
    
    # Verify file logging is disabled
    assert logging._should_log_to_file() is False


def test_log_with_both_logging_disabled():
    """Test log function returns early when both console and file are disabled.
    
    When both console and file logging are disabled, the log function should
    return without creating log records. This validates the early return
    optimization (line 244).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-logging param
    no_logging_param = param.get_param_by_name(LOG_NO_LOGGING_PARAM)
    config.set_config_value(no_logging_param, True)
    
    # This should return early without error
    logging.log(logging.INFO, _message='Should not be logged')


def test_apply_logging_config_with_log_level_param():
    """Test explicit log-level parameter sets both console and file levels.
    
    When an explicit log-level is specified, it should override all other
    level settings for both console and file. This validates the log-level
    parameter handling (lines 327-329).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set log-level param
    log_level_param = param.get_param_by_name(LOG_LEVEL_PARAM)
    config.set_config_value(log_level_param, 'WARNING')
    
    logging.apply_logging_config()
    
    # Both console and file should be at WARNING level
    assert logging._console_handler.level == stdlib_logging.WARNING
    assert logging._file_handler.level == stdlib_logging.WARNING


def test_apply_logging_config_with_trace_console():
    """Test trace-console parameter sets only console to TRACE.
    
    When trace-console is enabled, only console logging should be at TRACE
    level while file remains at default. This validates the trace-console
    parameter handling (line 338).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set trace-console param
    trace_console_param = param.get_param_by_name(LOG_TRACE_CONSOLE_PARAM)
    config.set_config_value(trace_console_param, True)
    
    logging.apply_logging_config()
    
    # Console should be TRACE, file should be DEBUG (default)
    assert logging._console_handler.level == logging.TRACE
    assert logging._file_handler.level == stdlib_logging.DEBUG


def test_apply_logging_config_with_silent():
    """Test silent parameter disables console logging.
    
    When silent mode is enabled, console logging should be set to a level
    above CRITICAL to effectively disable it. This validates the silent
    mode handling (line 346).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set silent param
    silent_param = param.get_param_by_name(LOG_SILENT_PARAM)
    config.set_config_value(silent_param, True)
    
    logging.apply_logging_config()
    
    # Console should be above CRITICAL (disabled)
    assert logging._console_handler.level > stdlib_logging.CRITICAL


def test_apply_logging_config_with_no_logging():
    """Test no-logging parameter disables both console and file.
    
    When no-logging mode is enabled, both console and file logging should
    be disabled. This validates the no-logging mode handling (lines 350-351).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-logging param
    no_logging_param = param.get_param_by_name(LOG_NO_LOGGING_PARAM)
    config.set_config_value(no_logging_param, True)
    
    logging.apply_logging_config()
    
    # Both should be above CRITICAL (disabled)
    assert logging._console_handler.level > stdlib_logging.CRITICAL
    assert logging._file_handler.level > stdlib_logging.CRITICAL


def test_apply_logging_config_with_no_file_logging():
    """Test no-file-logging parameter disables file logging.
    
    When no-file-logging is enabled, file logging should be disabled but
    console can remain active. This validates the no-file-logging handling
    (line 359).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set no-file-logging param
    no_file_logging_param = param.get_param_by_name(LOG_NO_FILE_LOGGING_PARAM)
    config.set_config_value(no_file_logging_param, True)
    
    logging.apply_logging_config()
    
    # File should be above CRITICAL (disabled)
    assert logging._file_handler.level > stdlib_logging.CRITICAL


def test_apply_logging_config_with_suppress_errors():
    """Test suppress-errors parameter enables error suppression.
    
    When suppress-errors is enabled, the logging system should suppress
    error output. This validates the suppress-errors handling (line 368).
    """
    # Add logging params
    param.add_params(LOGGING_PARAMS)
    
    # Get and set suppress-errors param
    suppress_errors_param = param.get_param_by_name(LOG_SUPPRESS_ERRORS_PARAM)
    config.set_config_value(suppress_errors_param, True)
    
    logging.apply_logging_config()
    
    # Verify suppress errors is enabled
    assert logging._suppress_errors is True
