#!/usr/bin/python3
# SPDX-License-Identifier: MIT

"""
This module contains unit tests for the kernel log functions in the amd-debug-tools package.
"""
from unittest.mock import patch, mock_open

import unittest
import logging

from amd_debug.kernel import sscanf_bios_args, get_kernel_command_line, DmesgLogger


class TestKernelLog(unittest.TestCase):
    """Test Common kernel scan functions"""

    @classmethod
    def setUpClass(cls):
        logging.basicConfig(filename="/dev/null", level=logging.DEBUG)

    def test_get_kernel_command_line(self):
        """Test get_kernel_command_line function"""

        # Test case with a valid kernel command line that is fully filtered
        kernel_cmdline = "quiet splash"
        expected_output = ""
        with patch(
            "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
        ) as _mock_file:
            result = get_kernel_command_line()
            self.assertEqual(result, expected_output)

        # Test case with an empty kernel command line
        kernel_cmdline = ""
        expected_output = ""
        with patch(
            "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
        ) as _mock_file:
            result = get_kernel_command_line()
            self.assertEqual(result, expected_output)

        # Test case with a kernel command line containing special characters
        kernel_cmdline = "quiet splash --debug=1"
        expected_output = "--debug=1"
        with patch(
            "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
        ) as _mock_file:
            result = get_kernel_command_line()
            self.assertEqual(result, expected_output)

        # Test case with a kernel command line containing special characters
        kernel_cmdline = "quiet splash initrd=foo modprobe.blacklist=foo"
        expected_output = "modprobe.blacklist=foo"
        with patch(
            "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
        ) as _mock_file:
            result = get_kernel_command_line()
            self.assertEqual(result, expected_output)

    def test_sscanf_bios_args(self):
        """Test sscanf_bios_args function"""

        # Test case with a valid line
        line = 'ex_trace_args: "format_string", 0x1234, 0x5678'
        expected_output = "format_string"
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)

        # Test case with an invalid line
        line = 'invalid_line: "format_string", 0x1234, 0x5678'
        result = sscanf_bios_args(line)
        self.assertIsNone(result)

        # Test case with a line containing "Unknown"
        line = 'ex_trace_args: "format_string", Unknown, 0x5678'
        expected_output = "format_string"
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)

        # make sure that lines with ex_trace_point are not parsed
        line = 'ex_trace_point: "format_string", 0x1234, 0x5678'
        result = sscanf_bios_args(line)
        self.assertTrue(result)

        # test a real post code line
        line = 'ex_trace_args: "  POST CODE: %X  ACPI TIMER: %X  TIME: %d.%d ms\\n", b0003f33, 83528798, 0, 77, 0, 0'
        expected_output = "POST CODE: B0003F33  ACPI TIMER: 83528798  TIME: 0.119 ms"
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)

        # test a real _REG print
        line = 'ex_trace_args:  "  OEM-ASL-PCIe Address (0x%X)._REG (%d %d)  PCSA = %d\\n", ec303000, 2, 0, 0, 0, 0'
        expected_output = "OEM-ASL-PCIe Address (0xEC303000)._REG (2 0)  PCSA = 0"
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)

        # test case of too may arguments
        line = 'ex_trace_args         :  "  APGE                  = %d\\n", 1, 0, 0, 0, 0, 0'
        expected_output = "APGE                  = 1"
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)

        # test case for Dispatch notify
        line = "evmisc-0132 ev_queue_notify_reques: Dispatching Notify on [UBTC] (Device) Value 0x80 (Status Change) Node 00000000851b15c1"
        expected_output = (
            "Dispatching Notify on [UBTC] (Device) Value 0x80 (Status Change)"
        )
        result = sscanf_bios_args(line)
        self.assertEqual(result, expected_output)


class TestDmesgLogger(unittest.TestCase):
    """Test Dmesg logger functions"""

    @classmethod
    @patch("subprocess.run")
    def setUpClass(cls, _mock_run=None):
        logging.basicConfig(filename="/dev/null", level=logging.DEBUG)

    def test_dmesg_logger_initialization(self):
        """Test initialization of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg -h
            mock_run.return_value.stdout = b"--since supported\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            self.assertTrue(logger.since_support)
            self.assertEqual(logger.command, ["dmesg", "-t", "-k"])

    def test_dmesg_logger_refresh_head(self):
        """Test _refresh_head method of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg
            mock_run.return_value.stdout = b"line1\nline2\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            logger._refresh_head()  # pylint: disable=protected-access
            self.assertEqual(logger.buffer, "line1\nline2\n")

    def test_dmesg_logger_seek_tail(self):
        """Test seek_tail method of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg
            mock_run.return_value.stdout = b"line1\nline2\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            logger.seek_tail()
            self.assertEqual(logger.buffer, "line1\nline2\n")

    def test_dmesg_logger_process_callback(self):
        """Test process_callback method of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg
            mock_run.return_value.stdout = b"line1\nline2\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            logger._refresh_head()  # pylint: disable=protected-access

            mock_callback = unittest.mock.Mock()
            logger.process_callback(mock_callback)

            mock_callback.assert_any_call("line1", None)
            mock_callback.assert_any_call("line2", None)

    def test_dmesg_logger_match_line(self):
        """Test match_line method of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg
            mock_run.return_value.stdout = b"line1\nline2\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            logger._refresh_head()  # pylint: disable=protected-access

            result = logger.match_line(["line1"])
            self.assertEqual(result, "line1")

            result = logger.match_line(["nonexistent"])
            self.assertEqual(result, "")

    def test_dmesg_logger_match_pattern(self):
        """Test match_pattern method of DmesgLogger"""

        with patch("subprocess.run") as mock_run:
            # Mock the subprocess output for dmesg
            mock_run.return_value.stdout = b"line1\nline2\n"
            mock_run.return_value.returncode = 0

            logger = DmesgLogger()
            logger._refresh_head()  # pylint: disable=protected-access

            result = logger.match_pattern(r"line\d")
            self.assertEqual(result, "line1")

            result = logger.match_pattern(r"nonexistent")
            self.assertEqual(result, "")
