"""
Test module for navlib.logging.logging

Authors: Sebastián Rodríguez-Martínez
Contact: srodriguez@mbari.org
"""

import io
import logging
import unittest

from navlib.logging.logging import _NavlibFormatter, setup_logging


class TestNavlibFormatter(unittest.TestCase):
    """Test cases for _NavlibFormatter class."""

    def setUp(self):
        """Set up test fixtures."""
        self.formatter = _NavlibFormatter()

    def test_default_initialization(self):
        """Test formatter initialization with default values."""
        formatter = _NavlibFormatter()
        self.assertEqual(formatter.app_name, "navlib")
        self.assertEqual(formatter.date_format, "%Y/%m/%d %H:%M:%S")

    def test_custom_initialization(self):
        """Test formatter initialization with custom values."""
        formatter = _NavlibFormatter(
            app_name="TestApp", date_format="%H:%M:%S", fmt="Custom format: %(message)s"
        )
        self.assertEqual(formatter.app_name, "TestApp")
        self.assertEqual(formatter.date_format, "%H:%M:%S")

    def test_format_message(self):
        """Test the format method."""
        formatter = _NavlibFormatter(app_name="TestApp")

        # Create a log record
        record = logging.LogRecord(
            name="test.logger",
            level=logging.INFO,
            pathname="",
            lineno=0,
            msg="Test message",
            args=(),
            exc_info=None,
        )

        formatted = formatter.format(record)

        # Check that the formatted message contains expected components
        self.assertIn("[INFO]", formatted)
        self.assertIn("[TestApp]", formatted)
        self.assertIn("[test.logger]", formatted)
        self.assertIn("Test message", formatted)

    def test_format_with_different_log_levels(self):
        """Test formatting with different log levels."""
        formatter = _NavlibFormatter(app_name="TestApp")

        levels = [
            (logging.DEBUG, "DEBUG"),
            (logging.INFO, "INFO"),
            (logging.WARNING, "WARNING"),
            (logging.ERROR, "ERROR"),
            (logging.CRITICAL, "CRITICAL"),
        ]

        for level_int, level_str in levels:
            record = logging.LogRecord(
                name="test",
                level=level_int,
                pathname="",
                lineno=0,
                msg="Test message",
                args=(),
                exc_info=None,
            )
            formatted = formatter.format(record)
            self.assertIn(f"[{level_str}]", formatted)


class TestSetupLogging(unittest.TestCase):
    """Test cases for setup_logging function."""

    def setUp(self):
        """Set up test fixtures."""
        # Clear any existing loggers before each test
        logging.getLogger().handlers.clear()
        for name in list(logging.Logger.manager.loggerDict.keys()):
            if name.startswith("test"):
                logging.getLogger(name).handlers.clear()

    def tearDown(self):
        """Clean up after each test."""
        # Clear loggers after each test
        logging.getLogger().handlers.clear()
        for name in list(logging.Logger.manager.loggerDict.keys()):
            if name.startswith("test"):
                logging.getLogger(name).handlers.clear()

    def test_default_setup(self):
        """Test setup_logging with default parameters."""
        logger = setup_logging()

        # Check logger properties
        self.assertEqual(logger.level, logging.INFO)  # level=2 maps to INFO
        self.assertEqual(len(logger.handlers), 1)
        self.assertIsInstance(logger.handlers[0], logging.StreamHandler)
        self.assertIsInstance(logger.handlers[0].formatter, _NavlibFormatter)

    def test_level_mapping(self):
        """Test the level mapping functionality."""
        level_mappings = [
            (0, logging.CRITICAL),
            (1, logging.DEBUG),
            (2, logging.INFO),
            (3, logging.WARNING),
            (4, logging.ERROR),
            (5, logging.NOTSET),
        ]

        for input_level, expected_level in level_mappings:
            logger = setup_logging(
                level=input_level, logger_name=f"test_level_{input_level}"
            )
            self.assertEqual(logger.level, expected_level)

    def test_invalid_level_type(self):
        """Test that non-integer levels raise ValueError."""
        with self.assertRaises(ValueError) as context:
            setup_logging(level="invalid")
        self.assertIn("Logging level must be an integer", str(context.exception))

    def test_invalid_level_value(self):
        """Test that invalid level values raise ValueError."""
        with self.assertRaises(ValueError) as context:
            setup_logging(level=10)
        self.assertIn("Invalid logging level", str(context.exception))

    def test_custom_app_name(self):
        """Test setup with custom app name."""
        logger = setup_logging(app_name="CustomApp", logger_name="test_custom_app")
        formatter = logger.handlers[0].formatter
        self.assertEqual(formatter.app_name, "CustomApp")

    def test_named_logger(self):
        """Test setup with named logger."""
        logger_name = "test.named.logger"
        logger = setup_logging(logger_name=logger_name)
        self.assertEqual(logger.name, logger_name)

    def test_custom_handler(self):
        """Test setup with custom handler."""
        custom_handler = logging.FileHandler("test.log")
        logger = setup_logging(
            handler=custom_handler, logger_name="test_custom_handler"
        )

        self.assertEqual(len(logger.handlers), 1)
        self.assertIs(logger.handlers[0], custom_handler)

        # Clean up
        custom_handler.close()
        import os

        if os.path.exists("test.log"):
            os.remove("test.log")

    def test_custom_formatter(self):
        """Test setup with custom formatter."""
        custom_formatter = logging.Formatter("Custom: %(message)s")
        logger = setup_logging(
            formatter=custom_formatter, logger_name="test_custom_formatter"
        )

        formatter = logger.handlers[0].formatter
        self.assertIs(formatter, custom_formatter)

    def test_clear_existing_true(self):
        """Test that existing handlers are cleared when clear_existing=True."""
        logger_name = "test_clear_true"

        # Set up logger with initial handler
        initial_logger = setup_logging(logger_name=logger_name)
        initial_handler_count = len(initial_logger.handlers)

        # Set up same logger again with clear_existing=True (default)
        new_logger = setup_logging(logger_name=logger_name, clear_existing=True)

        # Should have same number of handlers (old ones cleared, new one added)
        self.assertEqual(len(new_logger.handlers), initial_handler_count)

    def test_clear_existing_false(self):
        """Test that existing handlers are kept when clear_existing=False."""
        logger_name = "test_clear_false"

        # Set up logger with initial handler
        initial_logger = setup_logging(logger_name=logger_name)
        initial_handler_count = len(initial_logger.handlers)

        # Set up same logger again with clear_existing=False
        new_logger = setup_logging(logger_name=logger_name, clear_existing=False)

        # Should have more handlers (old ones kept, new one added)
        self.assertEqual(len(new_logger.handlers), initial_handler_count + 1)

    def test_logging_output(self):
        """Test that logging actually produces output."""
        # Capture stderr to test logging output
        captured_output = io.StringIO()

        # Create a handler that writes to our captured output
        test_handler = logging.StreamHandler(captured_output)

        logger = setup_logging(
            level=1,  # DEBUG level
            app_name="TestOutput",
            logger_name="test_output",
            handler=test_handler,
        )

        # Log some messages
        logger.debug("Debug message")
        logger.info("Info message")
        logger.warning("Warning message")
        logger.error("Error message")

        # Get the captured output
        output = captured_output.getvalue()

        # Check that all messages are present
        self.assertIn("Debug message", output)
        self.assertIn("Info message", output)
        self.assertIn("Warning message", output)
        self.assertIn("Error message", output)
        self.assertIn("[TestOutput]", output)
        self.assertIn("[test_output]", output)

    def test_return_value(self):
        """Test that setup_logging returns a Logger instance."""
        logger = setup_logging(logger_name="test_return")
        self.assertIsInstance(logger, logging.Logger)


class TestIntegration(unittest.TestCase):
    """Integration tests for the logging module."""

    def setUp(self):
        """Set up test fixtures."""
        # Clear any existing loggers
        logging.getLogger().handlers.clear()

    def tearDown(self):
        """Clean up after tests."""
        logging.getLogger().handlers.clear()

    def test_multiple_loggers(self):
        """Test setting up multiple loggers with different configurations."""
        # Set up different loggers
        nav_logger = setup_logging(
            level=1,  # DEBUG
            app_name="Navigation",
            logger_name="nav",
            clear_existing=False,
        )

        cal_logger = setup_logging(
            level=3,  # WARNING
            app_name="Calibration",
            logger_name="cal",
            clear_existing=False,
        )

        # Test that they work independently
        self.assertEqual(nav_logger.level, logging.DEBUG)
        self.assertEqual(cal_logger.level, logging.WARNING)
        self.assertEqual(nav_logger.name, "nav")
        self.assertEqual(cal_logger.name, "cal")

    def test_logger_hierarchy(self):
        """Test that logger hierarchy works correctly."""
        parent_logger = setup_logging(logger_name="parent", level=1)
        child_logger = setup_logging(
            logger_name="parent.child", level=2, clear_existing=False
        )

        # Both loggers should exist
        self.assertEqual(parent_logger.name, "parent")
        self.assertEqual(child_logger.name, "parent.child")


if __name__ == "__main__":
    unittest.main()
