"""Tests for lazy singleton initialization (Ticket #2).

Verifies that importing modules does NOT trigger heavy side effects
(DB connections, HTTP calls, sys.exit, directory creation) and that
lazy getters return stable, resettable singleton instances.
"""

import sys
from unittest.mock import patch

import pytest


class TestConfigLazyValidation:
    """config.py should not run startup validation at import time."""

    def test_import_config_does_not_exit(self):
        """Importing config should never call sys.exit, even in production."""
        with patch.dict("os.environ", {"TIMMY_ENV": "production"}, clear=False):
            # Re-import config — should not sys.exit
            if "config" in sys.modules:
                mod = sys.modules["config"]
                # validate_startup should exist as a callable
                assert callable(getattr(mod, "validate_startup", None)), (
                    "config.validate_startup() must exist as an explicit init path"
                )

    def test_validate_startup_exits_on_missing_secrets_in_production(self):
        """validate_startup() should exit in production when secrets are missing."""
        from config import settings, validate_startup

        with (
            patch.object(settings, "timmy_env", "production"),
            patch.object(settings, "l402_hmac_secret", ""),
            patch.object(settings, "l402_macaroon_secret", ""),
            pytest.raises(SystemExit),
        ):
            validate_startup(force=True)

    def test_validate_startup_ok_with_secrets(self):
        """validate_startup() should not exit when secrets are set."""
        from config import settings, validate_startup

        with (
            patch.object(settings, "timmy_env", "production"),
            patch.object(settings, "l402_hmac_secret", "test-secret-hex-value-32"),
            patch.object(settings, "l402_macaroon_secret", "test-macaroon-hex-value-32"),
        ):
            # Should not raise
            validate_startup(force=True)

    def test_validate_startup_exits_on_cors_wildcard_in_production(self):
        """validate_startup() should exit in production when CORS has wildcard."""
        from config import settings, validate_startup

        with (
            patch.object(settings, "timmy_env", "production"),
            patch.object(settings, "l402_hmac_secret", "test-secret-hex-value-32"),
            patch.object(settings, "l402_macaroon_secret", "test-macaroon-hex-value-32"),
            patch.object(settings, "cors_origins", ["*"]),
            pytest.raises(SystemExit),
        ):
            validate_startup(force=True)

    def test_validate_startup_warns_cors_wildcard_in_dev(self):
        """validate_startup() should warn in dev when CORS has wildcard."""
        from config import settings, validate_startup

        with (
            patch.object(settings, "timmy_env", "development"),
            patch.object(settings, "cors_origins", ["*"]),
            patch("config._startup_logger") as mock_logger,
        ):
            validate_startup(force=True)
            mock_logger.warning.assert_any_call(
                "SEC: CORS_ORIGINS contains wildcard '*' — "
                "restrict to explicit origins before deploying to production."
            )

    def test_validate_startup_skips_in_test_mode(self):
        """validate_startup() should be a no-op in test mode."""
        from config import validate_startup

        # TIMMY_TEST_MODE=1 is set by conftest — should not raise
        validate_startup()


class TestSparkEngineLazy:
    """spark.engine should not create the engine at import time."""

    def test_get_spark_engine_returns_instance(self):
        """get_spark_engine() should return a SparkEngine."""
        from spark.engine import SparkEngine, get_spark_engine

        engine = get_spark_engine()
        assert isinstance(engine, SparkEngine)

    def test_get_spark_engine_is_singleton(self):
        """Repeated calls return the same instance."""
        from spark.engine import get_spark_engine

        a = get_spark_engine()
        b = get_spark_engine()
        assert a is b

    def test_get_spark_engine_reset(self):
        """reset_spark_engine() allows re-initialization for tests."""
        from spark.engine import get_spark_engine, reset_spark_engine

        a = get_spark_engine()
        reset_spark_engine()
        b = get_spark_engine()
        assert a is not b

    def test_spark_engine_backward_compat(self):
        """spark_engine module-level name still works via get_spark_engine."""
        from spark.engine import spark_engine

        assert spark_engine is not None


class TestMemorySystemLazy:
    """timmy.memory_system should not create the system at import time."""

    def test_get_memory_system_returns_instance(self):
        """get_memory_system() should return a MemorySystem."""
        from timmy.memory_system import MemorySystem, get_memory_system

        ms = get_memory_system()
        assert isinstance(ms, MemorySystem)

    def test_get_memory_system_is_singleton(self):
        """Repeated calls return the same instance."""
        from timmy.memory_system import get_memory_system

        a = get_memory_system()
        b = get_memory_system()
        assert a is b

    def test_get_memory_system_reset(self):
        """reset_memory_system() allows re-initialization for tests."""
        from timmy.memory_system import get_memory_system, reset_memory_system

        a = get_memory_system()
        reset_memory_system()
        b = get_memory_system()
        assert a is not b

    def test_memory_system_backward_compat(self):
        """memory_system module-level name still works."""
        from timmy.memory_system import memory_system

        assert memory_system is not None


class TestEventBusLazy:
    """infrastructure.events.bus should use lazy initialization."""

    def test_get_event_bus_returns_instance(self):
        """get_event_bus() should return an EventBus."""
        from infrastructure.events.bus import EventBus, get_event_bus

        bus = get_event_bus()
        assert isinstance(bus, EventBus)

    def test_get_event_bus_is_singleton(self):
        """Repeated calls return the same instance."""
        from infrastructure.events.bus import get_event_bus

        a = get_event_bus()
        b = get_event_bus()
        assert a is b

    def test_event_bus_backward_compat(self):
        """event_bus module-level name still works."""
        from infrastructure.events.bus import event_bus

        assert event_bus is not None
