# tests/unit/services/test_module_service.py
"""
Unit tests for ModuleService.

Tests cover:
- Module enablement checking
- Module dependency resolution
- Menu item filtering by modules
- Platform module configuration
"""

import pytest

from app.modules import MODULES, ModuleDefinition, module_service
from app.modules.registry import (
    get_core_module_codes,
    get_menu_item_module,
    get_module,
    validate_module_dependencies,
)
from app.modules.service import ModuleService
from app.modules.enums import FrontendType


@pytest.mark.unit
@pytest.mark.service
class TestModuleRegistry:
    """Test module registry structure and validation."""

    def test_all_modules_defined(self):
        """Test that expected modules are defined."""
        expected_modules = {
            "core",
            "platform-admin",
            "billing",
            "inventory",
            "orders",
            "marketplace",
            "customers",
            "cms",
            "analytics",
            "messaging",
            "dev-tools",
            "monitoring",
        }
        assert set(MODULES.keys()) == expected_modules

    def test_core_modules_marked_correctly(self):
        """Test that core modules have is_core=True."""
        core_codes = get_core_module_codes()
        assert "core" in core_codes
        assert "platform-admin" in core_codes
        assert len(core_codes) == 2

    def test_module_dependencies_valid(self):
        """Test that all module dependencies reference valid modules."""
        errors = validate_module_dependencies()
        assert errors == [], f"Module dependency validation failed: {errors}"

    def test_marketplace_requires_inventory(self):
        """Test that marketplace module depends on inventory."""
        marketplace = get_module("marketplace")
        assert marketplace is not None
        assert "inventory" in marketplace.requires

    def test_core_modules_have_no_dependencies(self):
        """Test that core modules don't depend on optional modules."""
        for code in get_core_module_codes():
            module = get_module(code)
            assert module is not None
            # Core module dependencies should only be other core modules (or empty)
            for req in module.requires:
                assert req in get_core_module_codes(), (
                    f"Core module '{code}' depends on optional module '{req}'"
                )


@pytest.mark.unit
@pytest.mark.service
class TestModuleMenuMapping:
    """Test menu item to module mapping."""

    def test_dashboard_maps_to_core(self):
        """Test that dashboard menu item maps to core module."""
        module_code = get_menu_item_module("dashboard", FrontendType.ADMIN)
        assert module_code == "core"

    def test_billing_items_map_to_billing(self):
        """Test that billing menu items map to billing module."""
        billing_items = ["subscription-tiers", "subscriptions", "billing-history"]
        for item in billing_items:
            module_code = get_menu_item_module(item, FrontendType.ADMIN)
            assert module_code == "billing", f"{item} should map to billing, got {module_code}"

    def test_inventory_items_map_to_inventory(self):
        """Test that inventory menu items map to inventory module."""
        module_code = get_menu_item_module("inventory", FrontendType.ADMIN)
        assert module_code == "inventory"

    def test_marketplace_items_map_to_marketplace(self):
        """Test that marketplace menu items map to marketplace module."""
        module_code = get_menu_item_module("marketplace-letzshop", FrontendType.ADMIN)
        assert module_code == "marketplace"

    def test_vendor_billing_maps_to_billing(self):
        """Test that vendor billing menu item maps to billing module."""
        module_code = get_menu_item_module("billing", FrontendType.VENDOR)
        assert module_code == "billing"


@pytest.mark.unit
@pytest.mark.service
class TestModuleDefinition:
    """Test ModuleDefinition class."""

    def test_module_definition_equality(self):
        """Test that modules are equal by code."""
        mod1 = ModuleDefinition(code="test", name="Test 1")
        mod2 = ModuleDefinition(code="test", name="Test 2")
        mod3 = ModuleDefinition(code="other", name="Other")

        assert mod1 == mod2  # Same code
        assert mod1 != mod3  # Different code

    def test_module_has_feature(self):
        """Test has_feature method."""
        module = ModuleDefinition(
            code="test",
            name="Test",
            features=["feature_a", "feature_b"],
        )
        assert module.has_feature("feature_a")
        assert module.has_feature("feature_b")
        assert not module.has_feature("feature_c")

    def test_module_has_menu_item(self):
        """Test has_menu_item method."""
        module = ModuleDefinition(
            code="test",
            name="Test",
            menu_items={
                FrontendType.ADMIN: ["item-a", "item-b"],
                FrontendType.VENDOR: ["item-c"],
            },
        )
        assert module.has_menu_item("item-a")
        assert module.has_menu_item("item-c")
        assert not module.has_menu_item("item-d")

    def test_module_check_dependencies(self):
        """Test check_dependencies method."""
        module = ModuleDefinition(
            code="test",
            name="Test",
            requires=["dep1", "dep2"],
        )
        # All deps enabled
        assert module.check_dependencies({"dep1", "dep2", "dep3"}) == []
        # Missing dep1
        assert module.check_dependencies({"dep2"}) == ["dep1"]
        # Missing both
        assert set(module.check_dependencies(set())) == {"dep1", "dep2"}


@pytest.mark.unit
@pytest.mark.service
class TestModuleServiceWithPlatform:
    """Test ModuleService with database platform."""

    def test_get_platform_modules_all_enabled(self, db, test_platform):
        """Test that all modules are enabled when not configured."""
        # Platform has no enabled_modules setting, so all should be enabled
        service = ModuleService()
        modules = service.get_platform_modules(db, test_platform.id)

        # Should return all modules
        assert len(modules) == len(MODULES)

    def test_get_platform_modules_with_config(self, db, test_platform):
        """Test that only configured modules are enabled."""
        # Set enabled_modules in platform settings
        test_platform.settings = {"enabled_modules": ["billing", "inventory"]}
        db.commit()

        service = ModuleService()
        module_codes = service.get_enabled_module_codes(db, test_platform.id)

        # Should include core modules + configured modules
        assert "core" in module_codes
        assert "platform-admin" in module_codes
        assert "billing" in module_codes
        assert "inventory" in module_codes
        assert "marketplace" not in module_codes  # Not configured

    def test_is_module_enabled_core_always_enabled(self, db, test_platform):
        """Test that core modules are always enabled."""
        test_platform.settings = {"enabled_modules": []}  # Empty list
        db.commit()

        service = ModuleService()
        assert service.is_module_enabled(db, test_platform.id, "core")
        assert service.is_module_enabled(db, test_platform.id, "platform-admin")

    def test_is_module_enabled_optional_disabled(self, db, test_platform):
        """Test that optional modules can be disabled."""
        test_platform.settings = {"enabled_modules": ["inventory"]}
        db.commit()

        service = ModuleService()
        assert service.is_module_enabled(db, test_platform.id, "inventory")
        assert not service.is_module_enabled(db, test_platform.id, "billing")

    def test_dependency_resolution(self, db, test_platform):
        """Test that enabling marketplace auto-enables inventory."""
        # Enable marketplace but not inventory
        test_platform.settings = {"enabled_modules": ["marketplace"]}
        db.commit()

        service = ModuleService()
        module_codes = service.get_enabled_module_codes(db, test_platform.id)

        # Inventory should be auto-enabled due to marketplace dependency
        assert "marketplace" in module_codes
        assert "inventory" in module_codes

    def test_get_module_menu_items(self, db, test_platform):
        """Test getting menu items for enabled modules."""
        test_platform.settings = {"enabled_modules": ["billing"]}
        db.commit()

        service = ModuleService()
        menu_items = service.get_module_menu_items(db, test_platform.id, FrontendType.ADMIN)

        # Should include core and billing menu items
        assert "dashboard" in menu_items  # core
        assert "settings" in menu_items  # core
        assert "subscription-tiers" in menu_items  # billing
        assert "subscriptions" in menu_items  # billing
        # Should NOT include disabled module items
        assert "marketplace-letzshop" not in menu_items

    def test_is_menu_item_module_enabled(self, db, test_platform):
        """Test checking if menu item's module is enabled."""
        test_platform.settings = {"enabled_modules": ["billing"]}
        db.commit()

        service = ModuleService()

        # Billing menu item should be enabled
        assert service.is_menu_item_module_enabled(
            db, test_platform.id, "subscription-tiers", FrontendType.ADMIN
        )

        # Marketplace menu item should be disabled
        assert not service.is_menu_item_module_enabled(
            db, test_platform.id, "marketplace-letzshop", FrontendType.ADMIN
        )

    def test_filter_menu_items_by_modules(self, db, test_platform):
        """Test filtering menu items by enabled modules."""
        test_platform.settings = {"enabled_modules": ["billing"]}
        db.commit()

        service = ModuleService()

        # Try to filter a mix of enabled and disabled items
        all_items = {"dashboard", "subscription-tiers", "marketplace-letzshop", "inventory"}
        filtered = service.filter_menu_items_by_modules(
            db, test_platform.id, all_items, FrontendType.ADMIN
        )

        # Should keep core and billing items, remove marketplace and inventory
        assert "dashboard" in filtered
        assert "subscription-tiers" in filtered
        assert "marketplace-letzshop" not in filtered
        assert "inventory" not in filtered


@pytest.mark.unit
@pytest.mark.service
class TestModuleServiceEnableDisable:
    """Test module enable/disable operations."""

    def test_enable_module(self, db, test_platform):
        """Test enabling a module."""
        test_platform.settings = {"enabled_modules": ["billing"]}
        db.commit()

        service = ModuleService()
        result = service.enable_module(db, test_platform.id, "analytics")
        db.commit()

        assert result is True
        assert service.is_module_enabled(db, test_platform.id, "analytics")

    def test_disable_module(self, db, test_platform):
        """Test disabling a module."""
        test_platform.settings = {"enabled_modules": ["billing", "analytics"]}
        db.commit()

        service = ModuleService()
        result = service.disable_module(db, test_platform.id, "analytics")
        db.commit()

        assert result is True
        assert not service.is_module_enabled(db, test_platform.id, "analytics")
        assert service.is_module_enabled(db, test_platform.id, "billing")

    def test_cannot_disable_core_module(self, db, test_platform):
        """Test that core modules cannot be disabled."""
        service = ModuleService()
        result = service.disable_module(db, test_platform.id, "core")

        assert result is False
        assert service.is_module_enabled(db, test_platform.id, "core")

    def test_disable_module_cascades_to_dependents(self, db, test_platform):
        """Test that disabling a module also disables its dependents."""
        # Enable marketplace (which requires inventory)
        test_platform.settings = {"enabled_modules": ["marketplace", "inventory"]}
        db.commit()

        service = ModuleService()
        # Disable inventory - should also disable marketplace
        result = service.disable_module(db, test_platform.id, "inventory")
        db.commit()

        assert result is True
        assert not service.is_module_enabled(db, test_platform.id, "inventory")
        assert not service.is_module_enabled(db, test_platform.id, "marketplace")

    def test_set_enabled_modules(self, db, test_platform):
        """Test setting all enabled modules at once."""
        service = ModuleService()
        result = service.set_enabled_modules(
            db, test_platform.id, ["billing", "inventory", "orders"]
        )
        db.commit()

        assert result is True
        module_codes = service.get_enabled_module_codes(db, test_platform.id)

        # Should have core + specified modules
        assert "core" in module_codes
        assert "platform-admin" in module_codes
        assert "billing" in module_codes
        assert "inventory" in module_codes
        assert "orders" in module_codes
        assert "marketplace" not in module_codes

    def test_invalid_module_code_ignored(self, db, test_platform):
        """Test that invalid module codes are ignored."""
        service = ModuleService()
        result = service.set_enabled_modules(
            db, test_platform.id, ["billing", "invalid_module", "inventory"]
        )
        db.commit()

        assert result is True
        module_codes = service.get_enabled_module_codes(db, test_platform.id)

        assert "billing" in module_codes
        assert "inventory" in module_codes
        assert "invalid_module" not in module_codes


@pytest.mark.unit
@pytest.mark.service
class TestModuleServiceByCode:
    """Test ModuleService methods that work with platform code."""

    def test_get_platform_modules_by_code(self, db, test_platform):
        """Test getting modules by platform code."""
        service = ModuleService()
        modules = service.get_platform_modules_by_code(db, test_platform.code)

        # Should return all modules for platform without config
        assert len(modules) == len(MODULES)

    def test_is_module_enabled_by_code(self, db, test_platform):
        """Test checking module enablement by platform code."""
        test_platform.settings = {"enabled_modules": ["billing"]}
        db.commit()

        service = ModuleService()

        assert service.is_module_enabled_by_code(db, test_platform.code, "billing")
        assert service.is_module_enabled_by_code(db, test_platform.code, "core")
        assert not service.is_module_enabled_by_code(db, test_platform.code, "marketplace")

    def test_nonexistent_platform_code_returns_all(self, db):
        """Test that nonexistent platform code returns all modules."""
        service = ModuleService()
        modules = service.get_platform_modules_by_code(db, "nonexistent_platform")

        # Should return all modules as fallback
        assert len(modules) == len(MODULES)
