"""
Comprehensive tests for src/donor_dump/ kernel module functionality.
"""

import os
import re
import subprocess
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, Mock, mock_open, patch

import pytest

# Import the donor dump manager for testing
try:
    from src.donor_dump_manager import (
        DonorDumpError,
        DonorDumpManager,
        KernelHeadersNotFoundError,
        ModuleBuildError,
        ModuleLoadError,
    )
except ImportError:
    DonorDumpManager = None
    DonorDumpError = Exception
    KernelHeadersNotFoundError = Exception
    ModuleBuildError = Exception
    ModuleLoadError = Exception


@pytest.mark.unit
class TestKernelModuleBuild:
    """Test kernel module build process."""

    @patch("subprocess.run")
    @patch("os.chdir")
    def test_module_compilation_success(self, mock_chdir, mock_run):
        """Test successful kernel module compilation."""
        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        # Simulate the build process from build.py
        original_dir = os.getcwd()

        try:
            # This simulates what build.py does
            mock_chdir("src/donor_dump")
            mock_run("make clean", shell=True, check=True)
            mock_run("make", shell=True, check=True)

            # Verify build commands were called
            expected_calls = [
                Mock.call("make clean", shell=True, check=True),
                Mock.call("make", shell=True, check=True),
            ]
            mock_run.assert_has_calls(expected_calls)

        finally:
            mock_chdir(original_dir)

    @patch("subprocess.run")
    @patch("os.chdir")
    def test_module_compilation_failure(self, mock_chdir, mock_run):
        """Test kernel module compilation failure handling."""
        mock_run.side_effect = subprocess.CalledProcessError(1, "make")

        with pytest.raises(subprocess.CalledProcessError):
            mock_chdir("src/donor_dump")
            mock_run("make", shell=True, check=True)

    @patch("os.path.exists")
    def test_kernel_headers_check(self, mock_exists):
        """Test kernel headers availability check."""
        # Test when kernel headers are available
        mock_exists.return_value = True
        assert mock_exists("/lib/modules/$(uname -r)/build")

        # Test when kernel headers are missing
        mock_exists.return_value = False
        assert not mock_exists("/lib/modules/$(uname -r)/build")


@pytest.mark.unit
class TestModuleParameters:
    """Test kernel module parameter handling."""

    def test_bdf_parameter_validation(self):
        """Test BDF parameter validation."""
        valid_bdfs = ["0000:03:00.0", "0000:ff:1f.7", "abcd:12:34.5"]

        invalid_bdfs = [
            "000:03:00.0",  # Too short domain
            "0000:3:00.0",  # Too short bus
            "0000:03:0.0",  # Too short device
            "0000:03:00.8",  # Invalid function
            "invalid-bdf",  # Completely invalid
        ]

        # These would be validated by the kernel module
        # In tests, we simulate the validation logic
        import re

        bdf_pattern = re.compile(
            r"^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$"
        )

        for bdf in valid_bdfs:
            assert bdf_pattern.match(bdf), f"Valid BDF {bdf} should match pattern"

        for bdf in invalid_bdfs:
            assert not bdf_pattern.match(
                bdf
            ), f"Invalid BDF {bdf} should not match pattern"

    def test_module_parameter_parsing(self):
        """Test module parameter parsing simulation."""
        # Simulate module parameter parsing
        test_parameters = {
            "bdf": "0000:03:00.0",
            "enable_extended_config": "1",
            "enable_enhanced_caps": "1",
            "debug": "0",
        }

        # Validate parameter format
        for param, value in test_parameters.items():
            assert isinstance(param, str)
            assert isinstance(value, str)

            # BDF parameter should match expected format
            if param == "bdf":
                import re

                bdf_pattern = re.compile(
                    r"^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$"
                )
                assert bdf_pattern.match(value)

            # Boolean parameters should be 0 or 1
            elif param in ["enable_extended_config", "enable_enhanced_caps", "debug"]:
                assert value in ["0", "1"]


@pytest.mark.unit
class TestModuleLoading:
    """Test kernel module loading and unloading."""

    @patch("subprocess.run")
    def test_module_loading_success(self, mock_run):
        """Test successful module loading."""
        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        # Simulate insmod command
        bdf = "0000:03:00.0"
        mock_run(f"insmod donor_dump.ko bdf={bdf}", shell=True, check=True)

        mock_run.assert_called_once_with(
            f"insmod donor_dump.ko bdf={bdf}", shell=True, check=True
        )

    @patch("subprocess.run")
    def test_module_loading_failure(self, mock_run):
        """Test module loading failure handling."""
        mock_run.side_effect = subprocess.CalledProcessError(1, "insmod")

        with pytest.raises(subprocess.CalledProcessError):
            mock_run("insmod donor_dump.ko bdf=0000:03:00.0", shell=True, check=True)

    @patch("subprocess.run")
    def test_module_unloading(self, mock_run):
        """Test module unloading."""
        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        # Simulate rmmod command
        mock_run("rmmod donor_dump", shell=True, check=True)

        mock_run.assert_called_once_with("rmmod donor_dump", shell=True, check=True)

    @patch("subprocess.run")
    def test_module_already_loaded_handling(self, mock_run):
        """Test handling when module is already loaded."""
        # Simulate module already loaded error
        mock_run.side_effect = subprocess.CalledProcessError(1, "insmod", "File exists")

        with pytest.raises(subprocess.CalledProcessError):
            mock_run("insmod donor_dump.ko bdf=0000:03:00.0", shell=True, check=True)


@pytest.mark.unit
class TestProcInterface:
    """Test /proc interface functionality."""

    @patch("subprocess.check_output")
    def test_proc_donor_dump_output_parsing(self, mock_output):
        """Test parsing of /proc/donor_dump output."""
        # Mock typical /proc/donor_dump output
        mock_proc_output = """vendor_id: 0x8086
device_id: 0x1533
subvendor_id: 0x8086
subsystem_id: 0x0000
revision_id: 0x03
bar_size: 0x20000
mpc: 0x02
mpr: 0x02
extended_config_space: 1
enhanced_caps: 1"""

        mock_output.return_value = mock_proc_output

        # Parse the output (simulating build.py logic)
        info = {}
        for line in mock_proc_output.splitlines():
            if ":" in line:
                key, value = line.split(":", 1)
                info[key] = value.strip()

        # Verify parsed data
        assert info["vendor_id"] == "0x8086"
        assert info["device_id"] == "0x1533"
        assert info["bar_size"] == "0x20000"
        assert info["extended_config_space"] == "1"
        assert info["enhanced_caps"] == "1"

    @patch("subprocess.check_output")
    def test_proc_output_missing_fields(self, mock_output):
        """Test handling of missing fields in /proc output."""
        # Mock incomplete output
        mock_proc_output = """vendor_id: 0x8086
device_id: 0x1533"""

        mock_output.return_value = mock_proc_output

        # Parse the output
        info = {}
        for line in mock_proc_output.splitlines():
            if ":" in line:
                key, value = line.split(":", 1)
                info[key] = value.strip()

        # Check required fields
        required_fields = [
            "vendor_id",
            "device_id",
            "subvendor_id",
            "subsystem_id",
            "revision_id",
            "bar_size",
            "mpc",
            "mpr",
        ]

        missing_fields = [field for field in required_fields if field not in info]

        # Should detect missing fields
        assert len(missing_fields) > 0
        assert "bar_size" in missing_fields
        assert "mpc" in missing_fields

    @patch("subprocess.check_output")
    def test_proc_output_malformed(self, mock_output):
        """Test handling of malformed /proc output."""
        # Mock malformed output
        mock_proc_output = """malformed line without colon
vendor_id 0x8086
another malformed line"""

        mock_output.return_value = mock_proc_output

        # Parse the output
        info = {}
        for line in mock_proc_output.splitlines():
            if ":" in line:
                key, value = line.split(":", 1)
                info[key] = value.strip()

        # Should result in empty or minimal info
        assert len(info) == 0  # No valid lines with colons


@pytest.mark.unit
class TestDeviceAccess:
    """Test device access functionality."""

    def test_pci_config_space_access_simulation(self):
        """Test PCI configuration space access simulation."""
        # Simulate PCI config space data
        mock_config_data = {
            0x00: 0x8086,  # Vendor ID
            0x02: 0x1533,  # Device ID
            0x04: 0x0007,  # Command register
            0x06: 0x0010,  # Status register
            0x08: 0x03,  # Revision ID
            0x0A: 0x0200,  # Class code
            0x10: 0xF0000000,  # BAR0
            0x2C: 0x8086,  # Subsystem vendor ID
            0x2E: 0x0000,  # Subsystem ID
        }

        # Verify expected values
        assert mock_config_data[0x00] == 0x8086  # Intel vendor ID
        assert mock_config_data[0x02] == 0x1533  # I210 device ID
        assert mock_config_data[0x0A] == 0x0200  # Ethernet controller class

    def test_bar_size_calculation(self):
        """Test BAR size calculation logic."""
        # Simulate BAR size detection
        test_bars = [
            (0xF0000000, 0x20000),  # 128KB BAR
            (0xF0000000, 0x10000),  # 64KB BAR
            (0xF0000000, 0x1000),  # 4KB BAR
            (0xF0000000, 0x100000),  # 1MB BAR
        ]

        for bar_value, expected_size in test_bars:
            # BAR size calculation logic (simplified)
            # In real hardware, this involves writing all 1s and reading back
            size = expected_size  # Simulated result

            assert size > 0
            assert (size & (size - 1)) == 0  # Should be power of 2

    def test_extended_config_space_access(self):
        """Test extended configuration space access."""
        # Extended config space is 4KB (0x1000 bytes) vs standard 256 bytes
        standard_config_size = 0x100
        extended_config_size = 0x1000

        # Simulate extended capabilities
        mock_extended_caps = {
            0x100: 0x0001,  # PCIe capability
            0x140: 0x0005,  # MSI capability
            0x180: 0x0010,  # SR-IOV capability
        }

        # Verify extended space access
        for offset, value in mock_extended_caps.items():
            assert offset >= standard_config_size
            assert offset < extended_config_size
            assert value > 0


@pytest.mark.unit
class TestErrorHandling:
    """Test error handling in kernel module operations."""

    @patch("subprocess.run")
    def test_device_not_found_error(self, mock_run):
        """Test handling when target device is not found."""
        # Simulate device not found error
        mock_run.side_effect = subprocess.CalledProcessError(
            1, "insmod", "No such device"
        )

        with pytest.raises(subprocess.CalledProcessError):
            mock_run("insmod donor_dump.ko bdf=0000:99:00.0", shell=True, check=True)

    @patch("subprocess.run")
    def test_permission_denied_error(self, mock_run):
        """Test handling of permission denied errors."""
        # Simulate permission denied error
        mock_run.side_effect = subprocess.CalledProcessError(
            1, "insmod", "Operation not permitted"
        )

        with pytest.raises(subprocess.CalledProcessError):
            mock_run("insmod donor_dump.ko bdf=0000:03:00.0", shell=True, check=True)

    @patch("subprocess.run")
    def test_module_build_dependency_missing(self, mock_run):
        """Test handling when build dependencies are missing."""
        # Simulate missing kernel headers
        mock_run.side_effect = subprocess.CalledProcessError(
            2, "make", "No rule to make target"
        )

        with pytest.raises(subprocess.CalledProcessError):
            mock_run("make", shell=True, check=True)

    def test_invalid_bdf_parameter_handling(self):
        """Test handling of invalid BDF parameters."""
        invalid_bdfs = [
            "",
            "invalid",
            "0000:03:00.8",  # Invalid function number
            "0000:gg:00.0",  # Invalid hex characters
        ]

        import re

        bdf_pattern = re.compile(
            r"^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$"
        )

        for bdf in invalid_bdfs:
            # Module should reject invalid BDF formats
            assert not bdf_pattern.match(bdf)


@pytest.mark.integration
class TestModuleIntegration:
    """Test kernel module integration with the build system."""

    @patch("os.chdir")
    @patch("subprocess.run")
    @patch("subprocess.check_output")
    def test_full_module_workflow(
        self, mock_output, mock_run, mock_chdir, mock_donor_info
    ):
        """Test complete module workflow integration."""
        # Setup mocks for successful workflow
        mock_run.return_value = Mock(returncode=0)
        mock_output.return_value = """vendor_id: 0x8086
device_id: 0x1533
subvendor_id: 0x8086
subsystem_id: 0x0000
revision_id: 0x03
bar_size: 0x20000
mpc: 0x02
mpr: 0x02"""

        # Simulate the workflow from build.py
        bdf = "0000:03:00.0"

        # Change to module directory
        mock_chdir("src/donor_dump")

        # Build module
        mock_run("make -s", shell=True, check=True)

        # Load module
        mock_run(f"insmod donor_dump.ko bdf={bdf}", shell=True, check=True)

        # Read proc interface
        proc_output = mock_output("cat /proc/donor_dump", shell=True, text=True)

        # Unload module
        mock_run("rmmod donor_dump", shell=True, check=True)

        # Verify workflow
        assert mock_run.call_count >= 3  # make, insmod, rmmod
        assert proc_output is not None
        assert "vendor_id" in proc_output

    @patch("build.get_donor_info")
    def test_integration_with_build_system(self, mock_get_donor_info, mock_donor_info):
        """Test integration with the build system."""
        mock_get_donor_info.return_value = mock_donor_info

        # This would be called by build.py
        info = mock_get_donor_info("0000:03:00.0")

        # Verify required fields are present
        required_fields = [
            "vendor_id",
            "device_id",
            "subvendor_id",
            "subsystem_id",
            "revision_id",
            "bar_size",
            "mpc",
            "mpr",
        ]

        for field in required_fields:
            assert field in info
            assert info[field] is not None


@pytest.mark.performance
class TestModulePerformance:
    """Test kernel module performance characteristics."""

    def test_module_load_time_simulation(self):
        """Test module loading time simulation."""
        import time

        # Simulate module loading time
        start_time = time.time()

        # Simulate the operations that would occur during module loading
        time.sleep(0.01)  # Simulate 10ms load time

        load_time = time.time() - start_time

        # Module should load quickly (< 1 second)
        assert load_time < 1.0

    def test_proc_interface_read_performance(self):
        """Test /proc interface read performance."""
        import time

        # Simulate reading from /proc interface
        mock_data = "vendor_id: 0x8086\ndevice_id: 0x1533\n" * 100

        start_time = time.time()

        # Simulate parsing large proc output
        lines = mock_data.splitlines()
        info = {}
        for line in lines:
            if ":" in line:
                key, value = line.split(":", 1)
                info[key] = value.strip()

        parse_time = time.time() - start_time

        # Parsing should be fast (< 100ms)
        assert parse_time < 0.1
        assert len(info) > 0


@pytest.mark.hardware
class TestHardwareSimulation:
    """Test hardware simulation for CI environments."""

    def test_simulated_pci_device_data(self):
        """Test simulated PCI device data."""
        # Simulate common Intel network devices
        simulated_devices = [
            {"vendor": 0x8086, "device": 0x1533, "name": "I210 Gigabit Network"},
            {"vendor": 0x8086, "device": 0x15B7, "name": "Ethernet Connection"},
            {"vendor": 0x8086, "device": 0x1539, "name": "I211 Gigabit Network"},
        ]

        for device in simulated_devices:
            # Verify device data format
            assert isinstance(device["vendor"], int)
            assert isinstance(device["device"], int)
            assert isinstance(device["name"], str)

            # Verify Intel vendor ID
            assert device["vendor"] == 0x8086

            # Verify device ID is valid
            assert device["device"] > 0

    def test_simulated_config_space_data(self):
        """Test simulated configuration space data."""
        # Simulate typical config space for Intel I210
        config_space = {
            "vendor_id": 0x8086,
            "device_id": 0x1533,
            "command": 0x0007,
            "status": 0x0010,
            "revision_id": 0x03,
            "class_code": 0x020000,  # Ethernet controller
            "bar0": 0xF0000000,
            "bar1": 0x00000000,
            "subsystem_vendor": 0x8086,
            "subsystem_id": 0x0000,
        }

        # Verify config space structure
        assert config_space["vendor_id"] == 0x8086
        assert config_space["device_id"] == 0x1533
        assert config_space["class_code"] == 0x020000  # Network controller

        # Verify BAR is memory mapped
        assert config_space["bar0"] & 0x1 == 0  # Memory BAR (bit 0 = 0)


@pytest.mark.unit
class TestMakefileValidation:
    """Test Makefile validation and build configuration."""

    def test_makefile_exists(self):
        """Test that Makefile exists in donor_dump directory."""
        makefile_path = Path("src/donor_dump/Makefile")

        # Check if Makefile actually exists
        assert makefile_path.exists(), f"Makefile not found at {makefile_path}"
        assert (
            makefile_path.is_file()
        ), f"Makefile path exists but is not a file: {makefile_path}"

    def test_makefile_targets(self):
        """Test that Makefile has required targets."""
        makefile_path = Path("src/donor_dump/Makefile")
        required_targets = [
            "all",
            "clean",
            "install",
        ]  # Updated to match actual Makefile
        optional_targets = [
            "load",
            "unload",
            "info",
            "help",
        ]  # Additional useful targets

        # Read and parse the actual Makefile
        if makefile_path.exists():
            with open(makefile_path, "r") as f:
                makefile_content = f.read()

            # Check for each required target
            for target in required_targets:
                # Look for target definition (target: or target :)
                target_pattern = f"^{target}\\s*:"
                assert re.search(
                    target_pattern, makefile_content, re.MULTILINE
                ), f"Required target '{target}' not found in Makefile"

            # Check for optional targets (don't fail if missing, just log)
            found_optional = []
            for target in optional_targets:
                target_pattern = f"^{target}\\s*:"
                if re.search(target_pattern, makefile_content, re.MULTILINE):
                    found_optional.append(target)

            # Verify we found some optional targets (indicates a well-structured Makefile)
            assert (
                len(found_optional) > 0
            ), "No optional targets found - Makefile may be incomplete"

        else:
            # If Makefile doesn't exist, skip target validation but log warning
            pytest.skip(
                f"Makefile not found at {makefile_path}, skipping target validation"
            )

    def test_kernel_version_compatibility(self):
        """Test kernel version compatibility checks."""
        import platform
        import re

        # Get system info and use it for compatibility checking
        system_info = platform.uname()

        # Verify we can get kernel information
        assert system_info.system is not None
        assert system_info.release is not None

        # Parse kernel version for compatibility checking
        kernel_release = system_info.release

        # Extract major and minor version numbers
        version_match = re.match(r"^(\d+)\.(\d+)", kernel_release)
        if version_match:
            major, minor = map(int, version_match.groups())

            # Check for minimum kernel version requirements
            # Most PCIe direct memory access features require kernel 4.10+
            if major < 4 or (major == 4 and minor < 10):
                pytest.skip(
                    f"Kernel version {major}.{minor} may not support all required features (4.10+ recommended)"
                )

            # Check for known compatibility issues with specific kernel versions
            if major == 5 and 0 <= minor <= 3:
                # Log a warning about known compatibility issues
                print(
                    f"WARNING: Kernel 5.0-5.3 has known issues with VFIO passthrough for some devices"
                )

        # Extract major.minor version from kernel release
        version_match = re.match(r"(\d+)\.(\d+)", kernel_release)
        assert version_match, f"Could not parse kernel version from: {kernel_release}"

        major_version = int(version_match.group(1))
        minor_version = int(version_match.group(2))

        # Check compatibility with supported kernel versions
        # Most modern PCIe functionality requires kernel 3.0+
        min_major = 3
        min_minor = 0

        is_compatible = (major_version > min_major) or (
            major_version == min_major and minor_version >= min_minor
        )

        assert is_compatible, (
            f"Kernel version {major_version}.{minor_version} is below minimum "
            f"required version {min_major}.{min_minor} for PCIe module support"
        )

        # Additional checks for specific kernel features
        if major_version >= 4:
            # Kernel 4.0+ has enhanced PCIe support
            self._log_kernel_feature_support("Enhanced PCIe support available")

        if major_version >= 5:
            # Kernel 5.0+ has improved device tree and IOMMU support
            self._log_kernel_feature_support("Advanced IOMMU support available")

    def _log_kernel_feature_support(self, message: str) -> None:
        """Log kernel feature support information."""
        # Verify the message is valid
        assert isinstance(message, str)
        assert len(message) > 0

        # Enhanced logging functionality for better test diagnostics
        import logging
        import sys

        # Create a test-specific logger if it doesn't exist
        logger = logging.getLogger(f"{__name__}.kernel_features")

        # Configure logger for test output if not already configured
        if not logger.handlers:
            handler = logging.StreamHandler(sys.stdout)
            formatter = logging.Formatter(
                "[TEST] %(asctime)s - %(name)s - %(levelname)s - %(message)s"
            )
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.INFO)

        # Log the kernel feature support information
        logger.info(f"Kernel Feature Support: {message}")

        # Also capture for pytest output if running under pytest
        try:
            import pytest

            # Use pytest's built-in logging capture
            pytest.current_test_info = getattr(pytest, "current_test_info", {})
            pytest.current_test_info[
                f"kernel_feature_{len(pytest.current_test_info)}"
            ] = message
        except (ImportError, AttributeError):
            # pytest not available or not in test context, continue with standard logging
            pass


# Test markers for different test categories
pytestmark = [
    pytest.mark.unit,  # Default marker for this module
]


@pytest.mark.unit
class TestDonorDumpManager:
    """Test the DonorDumpManager class functionality."""

    def test_manager_initialization(self):
        """Test DonorDumpManager initialization."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        # Test default initialization
        manager = DonorDumpManager()
        assert manager.module_name == "donor_dump"
        assert manager.proc_path == "/proc/donor_dump"
        assert manager.module_source_dir.name == "donor_dump"

        # Test custom source directory
        custom_dir = Path("/tmp/test_donor_dump")
        manager = DonorDumpManager(custom_dir)
        assert manager.module_source_dir == custom_dir

    @patch("subprocess.check_output")
    @patch("os.path.exists")
    def test_check_kernel_headers_available(self, mock_exists, mock_check_output):
        """Test kernel headers check when headers are available."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_check_output.return_value = "5.15.0-generic\n"
        mock_exists.return_value = True

        manager = DonorDumpManager()
        headers_available, kernel_version = manager.check_kernel_headers()

        assert headers_available is True
        assert kernel_version == "5.15.0-generic"
        mock_exists.assert_called_with("/lib/modules/5.15.0-generic/build")

    @patch("subprocess.check_output")
    @patch("os.path.exists")
    def test_check_kernel_headers_missing(self, mock_exists, mock_check_output):
        """Test kernel headers check when headers are missing."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_check_output.return_value = "5.15.0-generic\n"
        mock_exists.return_value = False

        manager = DonorDumpManager()
        headers_available, kernel_version = manager.check_kernel_headers()

        assert headers_available is False
        assert kernel_version == "5.15.0-generic"

    @patch("subprocess.run")
    def test_install_kernel_headers_success(self, mock_run):
        """Test successful kernel headers installation."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_run.return_value = Mock(returncode=0)

        manager = DonorDumpManager()
        with patch.object(
            manager, "check_kernel_headers", return_value=(True, "5.15.0-generic")
        ):
            result = manager.install_kernel_headers("5.15.0-generic")

        assert result is True
        assert mock_run.call_count == 2  # apt-get update and install

    @patch("subprocess.run")
    def test_install_kernel_headers_failure(self, mock_run):
        """Test kernel headers installation failure."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_run.side_effect = subprocess.CalledProcessError(1, "apt-get")

        manager = DonorDumpManager()
        result = manager.install_kernel_headers("5.15.0-generic")

        assert result is False

    @patch("subprocess.run")
    @patch("pathlib.Path.exists")
    def test_build_module_success(self, mock_exists, mock_run):
        """Test successful module build."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        # Mock source directory exists, module doesn't exist yet
        mock_exists.side_effect = (
            lambda: mock_exists.call_count == 1
        )  # source dir exists, module doesn't
        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        manager = DonorDumpManager()
        with patch.object(
            manager, "check_kernel_headers", return_value=(True, "5.15.0-generic")
        ):
            result = manager.build_module()

        assert result is True
        mock_run.assert_called()

    @patch("pathlib.Path.exists")
    def test_build_module_source_missing(self, mock_exists):
        """Test module build when source directory is missing."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_exists.return_value = False

        manager = DonorDumpManager()

        with pytest.raises(ModuleBuildError, match="Module source directory not found"):
            manager.build_module()

    def test_build_module_headers_missing(self):
        """Test module build when kernel headers are missing."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        manager = DonorDumpManager()
        with patch.object(
            manager, "check_kernel_headers", return_value=(False, "5.15.0-generic")
        ):
            with patch.object(manager.module_source_dir, "exists", return_value=True):
                with pytest.raises(KernelHeadersNotFoundError):
                    manager.build_module()

    @patch("subprocess.run")
    def test_is_module_loaded_true(self, mock_run):
        """Test module loaded check when module is loaded."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_run.return_value = Mock(returncode=0, stdout="donor_dump 12345 0")

        manager = DonorDumpManager()
        result = manager.is_module_loaded()

        assert result is True

    @patch("subprocess.run")
    def test_is_module_loaded_false(self, mock_run):
        """Test module loaded check when module is not loaded."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_run.return_value = Mock(returncode=0, stdout="other_module 12345 0")

        manager = DonorDumpManager()
        result = manager.is_module_loaded()

        assert result is False

    def test_load_module_invalid_bdf(self):
        """Test module loading with invalid BDF format."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        manager = DonorDumpManager()

        with pytest.raises(ModuleLoadError, match="Invalid BDF format"):
            manager.load_module("invalid_bdf")

    @patch("subprocess.run")
    @patch("os.path.exists")
    @patch("pathlib.Path.exists")
    def test_load_module_success(self, mock_path_exists, mock_os_exists, mock_run):
        """Test successful module loading."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_path_exists.return_value = True  # Module file exists
        mock_os_exists.return_value = True  # Proc file exists
        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        manager = DonorDumpManager()
        with patch.object(manager, "is_module_loaded", side_effect=[False, True]):
            result = manager.load_module("0000:03:00.0")

        assert result is True

    @patch("subprocess.run")
    def test_unload_module_success(self, mock_run):
        """Test successful module unloading."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_run.return_value = Mock(returncode=0, stdout="", stderr="")

        manager = DonorDumpManager()
        with patch.object(manager, "is_module_loaded", return_value=True):
            result = manager.unload_module()

        assert result is True

    @patch(
        "builtins.open",
        new_callable=mock_open,
        read_data="vendor_id:8086\ndevice_id:1521\nclass_code:020000\n",
    )
    @patch("os.path.exists")
    def test_read_device_info_success(self, mock_exists, mock_file):
        """Test successful device info reading."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_exists.return_value = True

        manager = DonorDumpManager()
        device_info = manager.read_device_info()

        expected = {"vendor_id": "8086", "device_id": "1521", "class_code": "020000"}
        assert device_info == expected

    @patch("os.path.exists")
    def test_read_device_info_proc_missing(self, mock_exists):
        """Test device info reading when proc file is missing."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_exists.return_value = False

        manager = DonorDumpManager()

        with pytest.raises(DonorDumpError, match="Module not loaded"):
            manager.read_device_info()

    @patch("pathlib.Path.exists")
    @patch("pathlib.Path.stat")
    @patch("os.path.exists")
    def test_get_module_status(self, mock_os_exists, mock_stat, mock_path_exists):
        """Test module status reporting."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_path_exists.side_effect = [True, True]  # source dir and module file exist
        mock_stat.return_value = Mock(st_size=12345)
        mock_os_exists.return_value = True

        manager = DonorDumpManager()
        with patch.object(
            manager, "check_kernel_headers", return_value=(True, "5.15.0-generic")
        ):
            with patch.object(manager, "is_module_loaded", return_value=True):
                status = manager.get_module_status()

        assert status["kernel_version"] == "5.15.0-generic"
        assert status["headers_available"] is True
        assert status["module_built"] is True
        assert status["module_loaded"] is True
        assert status["proc_available"] is True
        assert status["source_dir_exists"] is True
        assert status["module_size"] == 12345

    @patch("json.dump")
    @patch("builtins.open", new_callable=mock_open)
    @patch("pathlib.Path.mkdir")
    def test_setup_module_complete_flow(self, mock_mkdir, mock_file, mock_json_dump):
        """Test complete module setup flow."""
        if DonorDumpManager is None:
            pytest.skip("DonorDumpManager not available")

        mock_device_info = {
            "vendor_id": "8086",
            "device_id": "1521",
            "class_code": "020000",
        }

        manager = DonorDumpManager()
        with patch.object(
            manager, "check_kernel_headers", return_value=(True, "5.15.0-generic")
        ):
            with patch.object(manager, "build_module", return_value=True):
                with patch.object(manager, "load_module", return_value=True):
                    with patch.object(
                        manager, "read_device_info", return_value=mock_device_info
                    ):
                        result = manager.setup_module("0000:03:00.0")

        assert result == mock_device_info
        mock_json_dump.assert_called_once()


@pytest.mark.integration
class TestDonorDumpIntegration:
    """Integration tests for donor dump functionality."""

    def test_cli_integration_import(self):
        """Test that CLI can import donor dump manager."""
        try:
            import generate

            # Check if the import was successful
            assert hasattr(generate, "DonorDumpManager")
        except ImportError:
            pytest.skip("generate module not available")

    def test_tui_config_integration(self):
        """Test TUI configuration includes donor dump fields."""
        try:
            from src.tui.models.config import BuildConfiguration

            config = BuildConfiguration()
            assert hasattr(config, "donor_dump")
            assert hasattr(config, "auto_install_headers")

            # Test CLI args conversion
            cli_args = config.to_cli_args()
            assert "donor_dump" in cli_args
            assert "auto_install_headers" in cli_args

            # Test serialization
            config_dict = config.to_dict()
            assert "donor_dump" in config_dict
            assert "auto_install_headers" in config_dict

        except ImportError:
            pytest.skip("TUI modules not available")

    def test_feature_summary_includes_donor_dump(self):
        """Test that feature summary includes donor dump when enabled."""
        try:
            from src.tui.models.config import BuildConfiguration

            config = BuildConfiguration(donor_dump=True)
            summary = config.feature_summary
            assert "Donor Device Analysis" in summary

        except ImportError:
            pytest.skip("TUI modules not available")


# Helper function for mock_open
def mock_open(*args, **kwargs):
    """Helper to create mock_open for file operations."""
    from unittest.mock import mock_open as _mock_open

    return _mock_open(*args, **kwargs)
