"""Tests for ALL module categories — import, BaseModule subclass, and attribute validation.

Covers: passive (17), active (9), intel (9), connectivity (8).
Stealth modules are already covered by test_stealth_modules.py.
"""

import importlib

import pytest

from modules.base import BaseModule


# ---------------------------------------------------------------------------
# Passive modules (17)
# ---------------------------------------------------------------------------

PASSIVE_MODULE_DOTTED = [
    "modules.passive.packet_capture",
    "modules.passive.dns_logger",
    "modules.passive.tls_sni_extractor",
    "modules.passive.credential_sniffer",
    "modules.passive.kerberos_harvester",
    "modules.passive.host_discovery",
    "modules.passive.os_fingerprint",
    "modules.passive.traffic_analyzer",
    "modules.passive.vlan_discovery",
    "modules.passive.network_mapper",
    "modules.passive.auth_flow_tracker",
    "modules.passive.smb_monitor",
    "modules.passive.cloud_token_harvester",
    "modules.passive.ldap_harvester",
    "modules.passive.rdp_monitor",
    "modules.passive.quic_analyzer",
    "modules.passive.db_interceptor",
]

PASSIVE_CLASS_NAMES = [
    "PacketCapture",
    "DNSLogger",
    "TLSSNIExtractor",
    "CredentialSniffer",
    "KerberosHarvester",
    "HostDiscovery",
    "OSFingerprint",
    "TrafficAnalyzer",
    "VLANDiscovery",
    "NetworkMapper",
    "AuthFlowTracker",
    "SMBMonitor",
    "CloudTokenHarvester",
    "LDAPHarvester",
    "RDPMonitor",
    "QUICAnalyzer",
    "DBInterceptor",
]


class TestPassiveModulesImportable:

    @pytest.mark.parametrize("dotted_path", PASSIVE_MODULE_DOTTED)
    def test_passive_module_importable(self, dotted_path):
        """Each passive module file can be imported without errors."""
        mod = importlib.import_module(dotted_path)
        assert mod is not None


class TestPassiveModulesAreBaseModule:

    @pytest.mark.parametrize("class_name", PASSIVE_CLASS_NAMES)
    def test_passive_modules_are_base_module(self, class_name):
        """Each passive module class is a subclass of BaseModule."""
        import modules.passive as passive_pkg
        cls = getattr(passive_pkg, class_name)
        assert issubclass(cls, BaseModule), f"{class_name} is not a BaseModule subclass"


class TestPassiveModuleAttributes:

    @pytest.mark.parametrize("class_name", PASSIVE_CLASS_NAMES)
    def test_passive_module_attributes(self, class_name):
        """Each passive module has required class attributes."""
        import modules.passive as passive_pkg
        cls = getattr(passive_pkg, class_name)

        assert hasattr(cls, "name") and cls.name != "unnamed"
        assert hasattr(cls, "module_type") and cls.module_type == "passive"
        assert hasattr(cls, "priority") and isinstance(cls.priority, int)
        assert hasattr(cls, "dependencies") and isinstance(cls.dependencies, list)
        assert hasattr(cls, "requires_root")

        for method_name in ("start", "stop", "status", "configure"):
            assert callable(getattr(cls, method_name, None))


class TestPassiveModuleInstantiate:

    @pytest.mark.parametrize("class_name", PASSIVE_CLASS_NAMES)
    def test_passive_module_instantiate(self, class_name, mock_bus, mock_state, mock_config):
        """Each passive module can be instantiated with mock bus/state/config."""
        import modules.passive as passive_pkg
        cls = getattr(passive_pkg, class_name)
        instance = cls(bus=mock_bus, state=mock_state, config=mock_config)
        assert instance is not None
        assert instance._running is False


# ---------------------------------------------------------------------------
# Active modules (9)
# ---------------------------------------------------------------------------

ACTIVE_MODULE_DOTTED = [
    "modules.active.bettercap_mgr",
    "modules.active.arp_spoof",
    "modules.active.dns_poison",
    "modules.active.dhcp_spoof",
    "modules.active.evil_twin",
    "modules.active.ipv6_slaac",
    "modules.active.responder_mgr",
    "modules.active.mitmproxy_mgr",
    "modules.active.ntlm_relay",
]

ACTIVE_CLASS_NAMES = [
    "BettercapManager",
    "ARPSpoof",
    "DNSPoison",
    "DHCPSpoof",
    "EvilTwin",
    "IPv6SLAAC",
    "ResponderManager",
    "MitmproxyManager",
    "NTLMRelay",
]


class TestActiveModulesImportable:

    @pytest.mark.parametrize("dotted_path", ACTIVE_MODULE_DOTTED)
    def test_active_module_importable(self, dotted_path):
        """Each active module file can be imported without errors."""
        mod = importlib.import_module(dotted_path)
        assert mod is not None


class TestActiveModulesAreBaseModule:

    @pytest.mark.parametrize("class_name", ACTIVE_CLASS_NAMES)
    def test_active_modules_are_base_module(self, class_name):
        """Each active module class is a subclass of BaseModule."""
        import modules.active as active_pkg
        cls = getattr(active_pkg, class_name)
        assert issubclass(cls, BaseModule), f"{class_name} is not a BaseModule subclass"


class TestActiveModuleAttributes:

    @pytest.mark.parametrize("class_name", ACTIVE_CLASS_NAMES)
    def test_active_module_attributes(self, class_name):
        """Each active module has required class attributes."""
        import modules.active as active_pkg
        cls = getattr(active_pkg, class_name)

        assert hasattr(cls, "name") and cls.name != "unnamed"
        assert hasattr(cls, "module_type") and cls.module_type == "active"
        assert hasattr(cls, "priority") and isinstance(cls.priority, int)
        assert hasattr(cls, "dependencies") and isinstance(cls.dependencies, list)
        assert hasattr(cls, "requires_root")

        for method_name in ("start", "stop", "status", "configure"):
            assert callable(getattr(cls, method_name, None))


class TestActiveModuleInstantiate:

    @pytest.mark.parametrize("class_name", ACTIVE_CLASS_NAMES)
    def test_active_module_instantiate(self, class_name, mock_bus, mock_state, mock_config):
        """Each active module can be instantiated with mock bus/state/config."""
        import modules.active as active_pkg
        cls = getattr(active_pkg, class_name)
        instance = cls(bus=mock_bus, state=mock_state, config=mock_config)
        assert instance is not None
        assert instance._running is False


# ---------------------------------------------------------------------------
# Intel modules (9)
# ---------------------------------------------------------------------------

INTEL_MODULE_DOTTED = [
    "modules.intel.credential_db",
    "modules.intel.topology_mapper",
    "modules.intel.net_intel",
    "modules.intel.user_timeline",
    "modules.intel.supply_chain_detect",
    "modules.intel.change_detector",
    "modules.intel.security_posture",
    "modules.intel.operator_audit",
    "modules.intel.tool_output_parser",
]

INTEL_CLASS_NAMES = [
    "CredentialDB",
    "TopologyMapper",
    "NetIntel",
    "UserTimeline",
    "SupplyChainDetect",
    "ChangeDetector",
    "SecurityPosture",
    "OperatorAudit",
    "ToolOutputParser",
]


class TestIntelModulesImportable:

    @pytest.mark.parametrize("dotted_path", INTEL_MODULE_DOTTED)
    def test_intel_module_importable(self, dotted_path):
        """Each intel module file can be imported without errors."""
        mod = importlib.import_module(dotted_path)
        assert mod is not None


class TestIntelModulesAreBaseModule:

    @pytest.mark.parametrize("class_name", INTEL_CLASS_NAMES)
    def test_intel_modules_are_base_module(self, class_name):
        """Each intel module class is a subclass of BaseModule."""
        import modules.intel as intel_pkg
        cls = getattr(intel_pkg, class_name)
        assert issubclass(cls, BaseModule), f"{class_name} is not a BaseModule subclass"


class TestIntelModuleAttributes:

    @pytest.mark.parametrize("class_name", INTEL_CLASS_NAMES)
    def test_intel_module_attributes(self, class_name):
        """Each intel module has required class attributes."""
        import modules.intel as intel_pkg
        cls = getattr(intel_pkg, class_name)

        assert hasattr(cls, "name") and cls.name != "unnamed"
        assert hasattr(cls, "module_type") and cls.module_type == "intel"
        assert hasattr(cls, "priority") and isinstance(cls.priority, int)
        assert hasattr(cls, "dependencies") and isinstance(cls.dependencies, list)
        assert hasattr(cls, "requires_root")

        for method_name in ("start", "stop", "status", "configure"):
            assert callable(getattr(cls, method_name, None))


class TestIntelModuleInstantiate:

    @pytest.mark.parametrize("class_name", INTEL_CLASS_NAMES)
    def test_intel_module_instantiate(self, class_name, mock_bus, mock_state, mock_config):
        """Each intel module can be instantiated with mock bus/state/config."""
        import modules.intel as intel_pkg
        cls = getattr(intel_pkg, class_name)
        instance = cls(bus=mock_bus, state=mock_state, config=mock_config)
        assert instance is not None
        assert instance._running is False


# ---------------------------------------------------------------------------
# Connectivity modules (8)
# ---------------------------------------------------------------------------

CONNECTIVITY_MODULE_DOTTED = [
    "modules.connectivity.wireguard",
    "modules.connectivity.tailscale",
    "modules.connectivity.bridge",
    "modules.connectivity.wifi_client",
    "modules.connectivity.cellular_backup",
    "modules.connectivity.ble_emergency",
    "modules.connectivity.data_exfil",
]

CONNECTIVITY_CLASS_NAMES = [
    "WireGuard",
    "Tailscale",
    "Bridge",
    "WiFiClient",
    "CellularBackup",
    "BLEEmergency",
    "DataExfil",
]


class TestConnectivityModulesImportable:

    @pytest.mark.parametrize("dotted_path", CONNECTIVITY_MODULE_DOTTED)
    def test_connectivity_module_importable(self, dotted_path):
        """Each connectivity module file can be imported without errors."""
        mod = importlib.import_module(dotted_path)
        assert mod is not None


class TestConnectivityModulesAreBaseModule:

    @pytest.mark.parametrize("class_name", CONNECTIVITY_CLASS_NAMES)
    def test_connectivity_modules_are_base_module(self, class_name):
        """Each connectivity module class is a subclass of BaseModule."""
        import modules.connectivity as conn_pkg
        cls = getattr(conn_pkg, class_name)
        assert issubclass(cls, BaseModule), f"{class_name} is not a BaseModule subclass"


class TestConnectivityModuleAttributes:

    @pytest.mark.parametrize("class_name", CONNECTIVITY_CLASS_NAMES)
    def test_connectivity_module_attributes(self, class_name):
        """Each connectivity module has required class attributes."""
        import modules.connectivity as conn_pkg
        cls = getattr(conn_pkg, class_name)

        assert hasattr(cls, "name") and cls.name != "unnamed"
        assert hasattr(cls, "module_type") and cls.module_type == "connectivity"
        assert hasattr(cls, "priority") and isinstance(cls.priority, int)
        assert hasattr(cls, "dependencies") and isinstance(cls.dependencies, list)
        assert hasattr(cls, "requires_root")

        for method_name in ("start", "stop", "status", "configure"):
            assert callable(getattr(cls, method_name, None))


class TestConnectivityModuleInstantiate:

    @pytest.mark.parametrize("class_name", CONNECTIVITY_CLASS_NAMES)
    def test_connectivity_module_instantiate(self, class_name, mock_bus, mock_state, mock_config):
        """Each connectivity module can be instantiated with mock bus/state/config."""
        import modules.connectivity as conn_pkg
        cls = getattr(conn_pkg, class_name)
        instance = cls(bus=mock_bus, state=mock_state, config=mock_config)
        assert instance is not None
        assert instance._running is False


# ---------------------------------------------------------------------------
# Cross-category: package __init__.py exports
# ---------------------------------------------------------------------------

class TestPackageExports:

    def test_passive_init_exports_all(self):
        """modules.passive.__init__.py exports all 17 classes."""
        import modules.passive as pkg
        assert len(pkg.__all__) == 17
        for name in pkg.__all__:
            cls = getattr(pkg, name)
            assert issubclass(cls, BaseModule)

    def test_active_init_exports_all(self):
        """modules.active.__init__.py exports all 9 classes."""
        import modules.active as pkg
        assert len(pkg.__all__) == 9
        for name in pkg.__all__:
            cls = getattr(pkg, name)
            assert issubclass(cls, BaseModule)

    def test_intel_init_exports_all(self):
        """modules.intel.__init__.py exports all 9 classes."""
        import modules.intel as pkg
        assert len(pkg.__all__) == 9
        for name in pkg.__all__:
            cls = getattr(pkg, name)
            assert issubclass(cls, BaseModule)

    def test_connectivity_init_exports_all(self):
        """modules.connectivity.__init__.py exports all 7 classes."""
        import modules.connectivity as pkg
        assert len(pkg.__all__) == 7
        for name in pkg.__all__:
            cls = getattr(pkg, name)
            assert issubclass(cls, BaseModule)

    def test_stealth_init_exports_all(self):
        """modules.stealth.__init__.py exports all 12 classes."""
        import modules.stealth as pkg
        assert len(pkg.__all__) == 12
        for name in pkg.__all__:
            cls = getattr(pkg, name)
            assert issubclass(cls, BaseModule)


# ---------------------------------------------------------------------------
# Discovery function test
# ---------------------------------------------------------------------------

class TestModuleDiscovery:

    def test_discover_all_modules(self):
        """discover_all_modules() finds all 55 modules across 5 categories."""
        from pathlib import Path
        import sys
        sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
        from bigbrother import discover_all_modules, discover_modules_in_category

        all_mods = discover_all_modules()
        # Stealth: 12, Passive: 17, Active: 9, Intel: 9, Connectivity: 7 = 54
        assert len(all_mods) == 54, f"Expected 54 modules, found {len(all_mods)}"

        # Verify per-category counts
        assert len(discover_modules_in_category("stealth")) == 12
        assert len(discover_modules_in_category("passive")) == 17
        assert len(discover_modules_in_category("active")) == 9
        assert len(discover_modules_in_category("intel")) == 9
        assert len(discover_modules_in_category("connectivity")) == 7

    def test_all_discovered_are_base_module(self):
        """Every discovered module is a BaseModule subclass."""
        from bigbrother import discover_all_modules

        for name, cls in discover_all_modules().items():
            assert issubclass(cls, BaseModule), f"{name} ({cls}) is not a BaseModule subclass"
