import unittest
from unittest.mock import patch, mock_open, MagicMock, Mock
import subprocess
from devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool import (
    DependencyCheckTool,
)

class TestDependencyCheckTool(unittest.TestCase):

    @patch("requests.get")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.Utils"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.open",
        new_callable=mock_open,
    )
    @patch("os.path.join")
    @patch("os.path.expanduser")
    def test_download_tool(
        self, mock_expanduser, mock_path_join, mock_open, mock_utils, mock_requests_get
    ):
        mock_expanduser.return_value = "/mock/home"
        mock_path_join.return_value = "/mock/home/dependency_check_7.0.zip"
        mock_requests_get.return_value.content = b"Fake Zip Content"

        tool = DependencyCheckTool()

        tool.download_tool("7.0")

        mock_requests_get.assert_called_with(
            "https://github.com/dependency-check/DependencyCheck/releases/download/v7.0/dependency-check-7.0-release.zip",
            allow_redirects=True,
        )

        mock_expanduser.assert_called_once()

        mock_path_join.assert_called_with("/mock/home", "dependency_check_7.0.zip")

        mock_open.assert_called_with("/mock/home/dependency_check_7.0.zip", "wb")

        mock_open().write.assert_called_once_with(b"Fake Zip Content")

        mock_utils.return_value.unzip_file.assert_called_with(
            "/mock/home/dependency_check_7.0.zip", "/mock/home"
        )

    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("os.path.join")
    @patch("os.path.expanduser")
    @patch("subprocess.run")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.download_tool"
    )
    def test_install_tool_already_installed(
        self,
        mock_download_tool,
        mock_subprocess_run,
        mock_expanduser,
        mock_path_join,
        mock_exists,
        mock_which,
    ):
        mock_which.return_value = "/mock/path/dependency-check.sh"

        tool = DependencyCheckTool()

        result = tool.install_tool("7.0")

        mock_download_tool.assert_not_called()

        self.assertEqual(result, "dependency-check.sh")

    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("os.path.join")
    @patch("os.path.expanduser")
    @patch("subprocess.run")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.download_tool"
    )
    def test_install_tool_not_installed_linux(
        self,
        mock_download_tool,
        mock_subprocess_run,
        mock_expanduser,
        mock_path_join,
        mock_exists,
        mock_which,
    ):
        mock_which.side_effect = [None, None]
        mock_expanduser.return_value = "/mock/home"
        mock_path_join.return_value = (
            "/mock/home/dependency-check/bin/dependency-check.sh"
        )
        mock_exists.return_value = True

        tool = DependencyCheckTool()

        result = tool.install_tool("7.0")

        mock_download_tool.assert_called_once_with("7.0")

        mock_subprocess_run.assert_called_once_with(
            ["chmod", "+x", "/mock/home/dependency-check/bin/dependency-check.sh"],
            check=True,
        )

        self.assertEqual(result, "/mock/home/dependency-check/bin/dependency-check.sh")

    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("os.path.join")
    @patch("os.path.expanduser")
    @patch("subprocess.run")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.download_tool"
    )
    def test_install_tool_windows(
        self,
        mock_download_tool,
        mock_subprocess_run,
        mock_expanduser,
        mock_path_join,
        mock_exists,
        mock_which,
    ):
        mock_which.side_effect = [None, None]
        mock_expanduser.return_value = "/mock/home"
        mock_path_join.return_value = (
            "/mock/home/dependency-check/bin/dependency-check.bat"
        )
        mock_exists.return_value = True

        tool = DependencyCheckTool()

        result = tool.install_tool("7.0", is_windows=True)

        mock_download_tool.assert_called_once_with("7.0")

        mock_subprocess_run.assert_not_called()

        self.assertEqual(result, "/mock/home/dependency-check/bin/dependency-check.bat")

    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("os.path.join")
    @patch("os.path.expanduser")
    @patch("subprocess.run")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.download_tool"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.logger.error"
    )
    def test_install_tool_error_handling(
        self,
        mock_logger_error,
        mock_download_tool,
        mock_subprocess_run,
        mock_expanduser,
        mock_path_join,
        mock_exists,
        mock_which,
    ):
        mock_which.side_effect = [None, None]
        mock_expanduser.return_value = "/mock/home"
        mock_path_join.return_value = (
            "/mock/home/dependency-check/bin/dependency-check.sh"
        )
        mock_exists.return_value = True
        mock_subprocess_run.side_effect = Exception("chmod failed")

        tool = DependencyCheckTool()

        result = tool.install_tool("7.0")

        mock_download_tool.assert_called_once_with("7.0")

        mock_logger_error.assert_called_once_with(
            "Error installing OWASP dependency check: chmod failed"
        )

        self.assertIsNone(result)

    @patch("subprocess.run")
    def test_scan_dependencies_success(self, mock_subprocess_run):
        mock_subprocess_run.return_value = MagicMock()

        tool = DependencyCheckTool()

        tool.scan_dependencies("dependency-check.sh", "mock_file_to_scan", "token")

        mock_subprocess_run.assert_called_once_with(
            [
                "dependency-check.sh",
                "--format",
                "XML",
                "--scan",
                "mock_file_to_scan",              
                "--nvdApiKey",
                "token",
                '--noupdate'
            ],
            capture_output=True,
            check=True,
            text=True
        )

    @patch("subprocess.run")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.logger.error"
    )
    def test_scan_dependencies_failure(self, mock_logger_error, mock_subprocess_run):
        mock_subprocess_run.side_effect = subprocess.CalledProcessError(
            returncode=1, cmd="dependency-check.sh", stderr="Mock Error"
        )

        tool = DependencyCheckTool()

        tool.scan_dependencies("dependency-check.sh", "mock_file_to_scan", "token")

        mock_logger_error.assert_called_once_with(
            "Error executing OWASP dependency check scan: Mock Error"
        )

        mock_subprocess_run.assert_called_once_with(
            [
                "dependency-check.sh",
                "--format",
                "XML",
                "--scan",
                "mock_file_to_scan",
                "--nvdApiKey",
                "token",
                '--noupdate'
            ],
            capture_output=True,
            check=True,
            text=True
        )

    @patch("platform.system")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.install_tool"
    )
    def test_select_operative_system_linux(
        self, mock_install_tool, mock_platform_system
    ):
        mock_platform_system.return_value = "Linux"

        tool = DependencyCheckTool()

        result = tool.select_operative_system("7.0")

        mock_install_tool.assert_called_once_with("7.0", is_windows=False)

        self.assertEqual(result, mock_install_tool.return_value)

    @patch("platform.system")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.install_tool"
    )
    def test_select_operative_system_windows(
        self, mock_install_tool, mock_platform_system
    ):
        mock_platform_system.return_value = "Windows"

        tool = DependencyCheckTool()

        tool.select_operative_system("7.0")

        mock_install_tool.assert_called_once_with("7.0", is_windows=True)

    @patch("platform.system")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.install_tool"
    )
    def test_select_operative_system_darwin(
        self, mock_install_tool, mock_platform_system
    ):
        mock_platform_system.return_value = "Darwin"

        tool = DependencyCheckTool()

        tool.select_operative_system("7.0")

        mock_install_tool.assert_called_once_with("7.0", is_windows=False)

    @patch("platform.system")
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.logger.warning"
    )
    def test_select_operative_system_unsupported(
        self, mock_logger_warning, mock_platform_system
    ):
        mock_platform_system.return_value = "UnsupportedOS"

        tool = DependencyCheckTool()

        result = tool.select_operative_system("7.0")

        mock_logger_warning.assert_called_once_with("UnsupportedOS is not supported.")

        self.assertIsNone(result)

    @patch("shutil.which")
    def test_is_java_installed_found(self, mock_which):
        mock_which.return_value = "/usr/bin/java"

        tool = DependencyCheckTool()

        result = tool.is_java_installed()

        mock_which.assert_called_once_with("java")

        self.assertTrue(result)

    @patch("shutil.which")
    def test_is_java_installed_not_found(self, mock_which):
        mock_which.return_value = None

        tool = DependencyCheckTool()

        result = tool.is_java_installed()

        mock_which.assert_called_once_with("java")

        self.assertFalse(result)

    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.is_java_installed"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.logger.error"
    )
    def test_run_tool_dependencies_sca_java_not_installed(
        self, mock_logger_error, mock_is_java_installed
    ):
        mock_is_java_installed.return_value = False

        tool = DependencyCheckTool()

        result = tool.run_tool_dependencies_sca(
            {}, {}, {}, "pipeline", "to_scan", "token", "token_engine_dependencies"
        )

        mock_logger_error.assert_called_once_with(
            "Java is not installed, please install it to run dependency check"
        )

        self.assertIsNone(result)

    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.is_java_installed"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.GetArtifacts"
    )
    def test_run_tool_dependencies_sca_no_artifacts_found(
        self, mock_get_artifacts, mock_is_java_installed
    ):
        mock_is_java_installed.return_value = True

        mock_get_artifacts.return_value.find_artifacts.return_value = []

        tool = DependencyCheckTool()

        remote_config = {
            "DEPENDENCY_CHECK": {"CLI_VERSION": "7.0", "PACKAGES_TO_SCAN": "packages"}
        }

        result = tool.run_tool_dependencies_sca(
            remote_config,
            {},
            {},
            "pipeline",
            "to_scan",
            "token",
            "token_engine_dependencies",
        )

        self.assertIsNone(result)

    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.is_java_installed"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.select_operative_system"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.scan_dependencies"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckTool.search_result"
    )
    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.GetArtifacts"
    )
    def test_run_tool_dependencies_sca_success(
        self,
        mock_get_artifacts,
        mock_search_result,
        mock_scan_dependencies,
        mock_select_operative_system,
        mock_is_java_installed,
    ):
        mock_is_java_installed.return_value = True

        mock_get_artifacts.return_value.excluded_files.return_value = "some_pattern"
        mock_get_artifacts.return_value.find_artifacts.return_value = [
            "artifact_to_scan"
        ]

        mock_select_operative_system.return_value = "dependency-check.sh"

        mock_search_result.return_value = {"key": "value"}

        tool = DependencyCheckTool()

        remote_config = {
            "DEPENDENCY_CHECK": {"CLI_VERSION": "7.0", "PACKAGES_TO_SCAN": "packages"}
        }

        result = tool.run_tool_dependencies_sca(
            remote_config,
            {},
            {},
            "pipeline",
            "to_scan",
            "token",
            "token_engine_dependencies",
        )

        mock_select_operative_system.assert_called_once_with("7.0")

        mock_scan_dependencies.assert_called_once_with(
            "dependency-check.sh", ["artifact_to_scan"], "token_engine_dependencies"
        )

        self.assertEqual(result, {"key": "value"})

    @patch(
        "devsecops_engine_tools.engine_sca.engine_dependencies.src.infrastructure.driven_adapters.dependency_check.dependency_check_tool.DependencyCheckDeserialize"
    )
    def test_get_dependencies_context_from_results_prints(self, mock_deserializer_cls):
        # Arrange
        tool = DependencyCheckTool()
        mock_deserializer = MagicMock()
        mock_deserializer_cls.return_value = mock_deserializer

        mock_dependency = MagicMock()
        mock_namespace = {"ns": "namespace"}
        mock_deserializer.filter_vulnerabilities_by_confidence.return_value = ([mock_dependency], mock_namespace)

        mock_vuln_node = MagicMock()
        mock_vuln_node.findall.return_value = [mock_vuln_node]
        mock_vuln_node.find.side_effect = lambda tag, ns=None: {
            'ns:name': MagicMock(text="CVE-2024-1234"),
            'ns:description': MagicMock(text="Test description"),
            'ns:severity': MagicMock(text="HIGH"),
            'ns:references': None,
            'ns:vulnerableSoftware': None
        }.get(tag, None)

        mock_vulns_node = MagicMock()
        mock_vulns_node.findall.return_value = [mock_vuln_node]
        mock_dependency.find.side_effect = lambda tag, ns=None: {
            'ns:vulnerabilities': mock_vulns_node
        }.get(tag, None)

        mock_deserializer.extract_common_vuln_data.return_value = {
            "id": "CVE-2024-1234",
            "fix": "1.2.3",
            "where": "pkg:example:1.2.0",
            "description": "Test description",
            "severity": "high"
        }
        mock_deserializer.extract_references.return_value = ["https://vuln.com/CVE-2024-1234"]

        remote_config = {
            "DEPENDENCY_CHECK": {
                "VULNERABILITY_CONFIDENCE": ["high", "medium", "low"]
            }
        }

        # Act
        result = tool.get_dependencies_context_from_results("fake_path.xml", remote_config)

        # Assert
        self.assertIsInstance(result, list)
        self.assertEqual(len(result), 1)
        self.assertEqual(result[0].cve_id, ["CVE-2024-1234"])
        self.assertEqual(result[0].severity, "high")
        self.assertIn("Test description", result[0].description)