"""
This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""

import logging
from unittest.mock import mock_open, patch

import pytest

from control_backend.logging.setup_logging import add_logging_level, setup_logging


def test_add_logging_level():
    # Add a unique level to avoid conflicts with other tests/libraries
    level_name = "TESTLEVEL"
    level_num = 35

    add_logging_level(level_name, level_num)

    assert logging.getLevelName(level_num) == level_name
    assert hasattr(logging, level_name)
    assert hasattr(logging.getLoggerClass(), level_name.lower())

    # Test functionality
    logger = logging.getLogger("test_custom_level")
    with patch.object(logger, "_log") as mock_log:
        getattr(logger, level_name.lower())("message")
        mock_log.assert_called_with(level_num, "message", ())

    # Test duplicates
    with pytest.raises(AttributeError):
        add_logging_level(level_name, level_num)

    with pytest.raises(AttributeError):
        add_logging_level("INFO", 20)  # Existing level


def test_setup_logging_no_file(caplog):
    with patch("os.path.exists", return_value=False):
        setup_logging("dummy.yaml")
    assert "Logging config file not found" in caplog.text


def test_setup_logging_yaml_error(caplog):
    with patch("os.path.exists", return_value=True):
        with patch("builtins.open", mock_open(read_data="invalid: [yaml")):
            with patch("logging.config.dictConfig") as mock_dict_config:
                setup_logging("config.yaml")

                # Verify we logged the warning
                assert "Could not load logging configuration" in caplog.text
                # Verify dictConfig was called with empty dict (which would crash real dictConfig)
                mock_dict_config.assert_called_with({})
    assert "Could not load logging configuration" in caplog.text


def test_setup_logging_success():
    config_data = """
    version: 1
    handlers:
      console:
        class: logging.StreamHandler
    root:
      handlers: [console]
      level: INFO
    custom_levels:
      MYLEVEL: 15
    """
    with patch("os.path.exists", return_value=True):
        with patch("builtins.open", mock_open(read_data=config_data)):
            with patch("logging.config.dictConfig") as mock_dict_config:
                setup_logging("config.yaml")
                mock_dict_config.assert_called()
                assert hasattr(logging, "MYLEVEL")


def test_setup_logging_zmq_handler(mock_zmq_context):
    config_data = """
    version: 1
    handlers:
      ui:
        class: logging.NullHandler 
        # In real config this would be a zmq handler, but for unit test logic
        # we just want to see if the socket injection happens
    """
    with patch("os.path.exists", return_value=True):
        with patch("builtins.open", mock_open(read_data=config_data)):
            with patch("logging.config.dictConfig") as mock_dict_config:
                setup_logging("config.yaml")

                args = mock_dict_config.call_args[0][0]
                assert "interface_or_socket" in args["handlers"]["ui"]


def test_add_logging_level_method_name_exists_in_logging():
    # method_name explicitly set to an existing logging method → triggers first hasattr branch
    with pytest.raises(AttributeError) as exc:
        add_logging_level("NEWDUPLEVEL", 37, method_name="info")
    assert "info already defined in logging module" in str(exc.value)


def test_add_logging_level_method_name_exists_in_logger_class():
    # 'makeRecord' exists on Logger class but not on the logging module
    with pytest.raises(AttributeError) as exc:
        add_logging_level("ANOTHERLEVEL", 38, method_name="makeRecord")
    assert "makeRecord already defined in logger class" in str(exc.value)


def test_add_logging_level_log_to_root_path_executes_without_error():
    # Verify log_to_root is installed and callable — without asserting logging output
    level_name = "ROOTTEST"
    level_num = 36

    add_logging_level(level_name, level_num)

    # Simply call the injected root logger method
    # The line is executed even if we don't validate output
    root_logging_method = getattr(logging, level_name.lower(), None)
    assert callable(root_logging_method)

    # Execute the method to hit log_to_root in coverage.
    # No need to verify log output.
    root_logging_method("some message")
