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

"""
This module contains unit tests for the bios tool in the amd-debug-tools package.
"""
import argparse
import logging
import unittest
from unittest.mock import patch, MagicMock

from amd_debug.bios import AmdBios, parse_args, main


class TestAmdBios(unittest.TestCase):
    """Test AmdBios class"""

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

    @patch("amd_debug.bios.get_kernel_log")
    def test_init(self, mock_get_kernel_log):
        """Test initialization of AmdBios class"""
        mock_kernel_log = MagicMock()
        mock_get_kernel_log.return_value = mock_kernel_log

        app = AmdBios("test_input", debug=True)

        self.assertEqual(app.kernel_log, mock_kernel_log)

    @patch("amd_debug.bios.relaunch_sudo")
    @patch("amd_debug.bios.minimum_kernel")
    @patch("amd_debug.bios.AcpicaTracer")
    @patch("amd_debug.bios.print_color")
    @patch("subprocess.run")
    def test_set_tracing_enable(
        self,
        _mock_run,
        _mock_print,
        mock_acpica_tracer,
        mock_minimum_kernel,
        mock_relaunch_sudo,
    ):
        """Test enabling tracing"""
        mock_minimum_kernel.return_value = True
        mock_tracer = MagicMock()
        mock_acpica_tracer.return_value = mock_tracer
        mock_tracer.trace_bios.return_value = True

        app = AmdBios(None, debug=False)
        result = app.set_tracing(enable=True)

        mock_relaunch_sudo.assert_called_once()
        mock_tracer.trace_bios.assert_called_once()
        self.assertTrue(result)

    @patch("amd_debug.bios.relaunch_sudo")
    @patch("amd_debug.bios.minimum_kernel")
    @patch("amd_debug.bios.AcpicaTracer")
    @patch("amd_debug.bios.print_color")
    @patch("subprocess.run")
    def test_set_tracing_disable(
        self,
        _mock_run,
        _mock_print,
        mock_acpica_tracer,
        mock_minimum_kernel,
        mock_relaunch_sudo,
    ):
        """Test disabling tracing"""
        mock_minimum_kernel.return_value = True
        mock_tracer = MagicMock()
        mock_acpica_tracer.return_value = mock_tracer
        mock_tracer.disable.return_value = True

        app = AmdBios(None, debug=False)
        result = app.set_tracing(enable=False)

        mock_relaunch_sudo.assert_called_once()
        mock_tracer.disable.assert_called_once()
        self.assertTrue(result)

    @patch("amd_debug.bios.sscanf_bios_args")
    @patch("amd_debug.bios.print_color")
    @patch("subprocess.run")
    def test_analyze_kernel_log_line(
        self, _mock_run, mock_print_color, mock_sscanf_bios_args
    ):
        """Test analyzing kernel log line"""
        mock_sscanf_bios_args.return_value = "BIOS argument found"

        app = AmdBios(None, debug=False)
        app._analyze_kernel_log_line(  # pylint: disable=protected-access
            "test log line", priority="INFO"
        )

        mock_sscanf_bios_args.assert_called_once_with("test log line")
        mock_print_color.assert_called_once_with("BIOS argument found", "🖴")

    @patch("amd_debug.bios.sscanf_bios_args")
    @patch("amd_debug.bios.print_color")
    @patch("subprocess.run")
    def test_analyze_kernel_log_line_no_bios_args(
        self, _mock_run, mock_print_color, mock_sscanf_bios_args
    ):
        """Test analyzing kernel log line with no BIOS arguments"""
        mock_sscanf_bios_args.return_value = None

        app = AmdBios(None, debug=False)
        app._analyze_kernel_log_line(  # pylint: disable=protected-access
            "[123.456] test log line", priority="INFO"
        )

        mock_sscanf_bios_args.assert_called_once_with("[123.456] test log line")
        mock_print_color.assert_called_once_with("test log line", "INFO")

    @patch("amd_debug.bios.get_kernel_log")
    def test_run(self, _mock_run):
        """Test run method"""
        mock_kernel_log = MagicMock()
        app = AmdBios(None, debug=False)
        app.kernel_log = mock_kernel_log

        app.run()

        mock_kernel_log.process_callback.assert_called_once_with(
            app._analyze_kernel_log_line  # pylint: disable=protected-access
        )

    @patch("sys.argv", ["bios.py", "parse", "--input", "test.log", "--tool-debug"])
    def test_parse_args_parse_command(self):
        """Test parse_args with parse command"""

        args = parse_args()
        self.assertEqual(args.command, "parse")
        self.assertEqual(args.input, "test.log")
        self.assertTrue(args.tool_debug)

    @patch("sys.argv", ["bios.py", "trace", "--enable", "--tool-debug"])
    def test_parse_args_trace_enable(self):
        """Test parse_args with trace enable command"""

        args = parse_args()
        self.assertEqual(args.command, "trace")
        self.assertTrue(args.enable)
        self.assertFalse(args.disable)
        self.assertTrue(args.tool_debug)

    @patch("sys.argv", ["bios.py", "trace", "--disable"])
    def test_parse_args_trace_disable(self):
        """Test parse_args with trace disable command"""

        args = parse_args()
        self.assertEqual(args.command, "trace")
        self.assertFalse(args.enable)
        self.assertTrue(args.disable)

    @patch("sys.argv", ["bios.py", "--version"])
    def test_parse_args_version_command(self):
        """Test parse_args with version command"""

        args = parse_args()
        self.assertTrue(args.version)

    @patch("sys.argv", ["bios.py"])
    @patch("argparse.ArgumentParser.print_help")
    @patch("sys.exit")
    def test_parse_args_no_arguments(self, mock_exit, mock_print_help):
        """Test parse_args with no arguments"""

        parse_args()
        mock_print_help.assert_called_once()
        mock_exit.assert_called_once_with(1)

    @patch("sys.argv", ["bios.py", "trace", "--enable", "--disable"])
    @patch("sys.exit")
    def test_parse_args_conflicting_trace_arguments(self, mock_exit):
        """Test parse_args with conflicting trace arguments"""

        parse_args()
        mock_exit.assert_called_once_with("can't set both enable and disable")

    @patch("sys.argv", ["bios.py", "trace"])
    @patch("sys.exit")
    def test_parse_args_missing_trace_arguments(self, mock_exit):
        """Test parse_args with missing trace arguments"""

        parse_args()
        mock_exit.assert_called_once_with("must set either enable or disable")

    @patch("amd_debug.bios.AmdBios")
    @patch("amd_debug.bios.parse_args")
    @patch("amd_debug.bios.version")
    @patch("amd_debug.bios.show_log_info")
    def test_main_trace_command(
        self, mock_show_log_info, _mock_version, mock_parse_args, mock_amd_bios
    ):
        """Test main function with trace command"""
        mock_app = MagicMock()
        mock_amd_bios.return_value = mock_app
        mock_parse_args.return_value = argparse.Namespace(
            command="trace", enable=True, disable=False, tool_debug=True
        )
        mock_app.set_tracing.return_value = True

        result = main()

        mock_parse_args.assert_called_once()
        mock_amd_bios.assert_called_once_with(None, True)
        mock_app.set_tracing.assert_called_once_with(True)
        mock_show_log_info.assert_called_once()
        self.assertIsNone(result)

    @patch("amd_debug.bios.AmdBios")
    @patch("amd_debug.bios.parse_args")
    @patch("amd_debug.bios.version")
    @patch("amd_debug.bios.show_log_info")
    def test_main_parse_command(
        self, mock_show_log_info, _mock_version, mock_parse_args, mock_amd_bios
    ):
        """Test main function with parse command"""
        mock_app = MagicMock()
        mock_amd_bios.return_value = mock_app
        mock_parse_args.return_value = argparse.Namespace(
            command="parse", input="test.log", tool_debug=True
        )
        mock_app.run.return_value = True

        result = main()

        mock_parse_args.assert_called_once()
        mock_amd_bios.assert_called_once_with("test.log", True)
        mock_app.run.assert_called_once()
        mock_show_log_info.assert_called_once()
        self.assertIsNone(result)

    @patch("amd_debug.bios.parse_args")
    @patch("amd_debug.bios.version")
    @patch("amd_debug.bios.show_log_info")
    @patch("amd_debug.bios.print")
    def test_main_version_command(
        self, _mock_print, mock_show_log_info, mock_version, mock_parse_args
    ):
        """Test main function with version command"""
        mock_parse_args.return_value = argparse.Namespace(version=True, command=None)
        mock_version.return_value = "1.0.0"

        result = main()

        mock_parse_args.assert_called_once()
        mock_version.assert_called_once()
        mock_show_log_info.assert_called_once()
        self.assertEqual(result, 1)

    @patch("amd_debug.bios.parse_args")
    @patch("amd_debug.bios.show_log_info")
    def test_main_invalid_command(self, mock_show_log_info, mock_parse_args):
        """Test main function with an invalid command"""
        mock_parse_args.return_value = argparse.Namespace(
            version=False, command="invalid"
        )

        result = main()

        mock_parse_args.assert_called_once()
        mock_show_log_info.assert_called_once()
        self.assertEqual(result, 1)
