"""Tests for MemoryRuntime lifecycle ownership and initialization hardening."""

from __future__ import annotations

import asyncio
import concurrent.futures
import threading
import time
from unittest.mock import MagicMock, patch

import pytest


class TestMemoryRuntimeLifecycle:
    def test_shutdown_preserves_other_runtime_clients(self):
        from consolidation_memory.client import MemoryClient
        from consolidation_memory.database import ensure_schema
        from consolidation_memory.runtime import MemoryRuntime

        ensure_schema()
        runtime_a = MemoryRuntime(client_factory=lambda: MemoryClient(auto_consolidate=False))
        runtime_b = MemoryRuntime(client_factory=lambda: MemoryClient(auto_consolidate=False))
        runtime_a.startup()
        runtime_b.startup()

        with patch("consolidation_memory.database.close_all_connections") as mock_close_all:
            client_a = runtime_a.get_client(wait_timeout=1.0)
            client_b = runtime_b.get_client(wait_timeout=1.0)
            assert runtime_a.client is client_a

            runtime_a.shutdown()
            assert mock_close_all.call_count == 0
            assert client_b.status().version != ""

            runtime_b.shutdown()

    def test_timed_out_client_init_does_not_wedge_shutdown(self):
        from consolidation_memory.runtime import MemoryRuntime

        entered = threading.Event()
        release = threading.Event()
        mock_client = MagicMock()

        def _slow_factory():
            entered.set()
            release.wait(timeout=5.0)
            return mock_client

        runtime = MemoryRuntime(client_factory=_slow_factory)
        runtime.startup()

        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
            future = pool.submit(runtime.get_client, wait_timeout=0.05)
            assert entered.wait(timeout=1.0)
            with pytest.raises(TimeoutError, match="timed out"):
                future.result(timeout=1.0)

        started = time.monotonic()
        runtime.shutdown()
        elapsed = time.monotonic() - started
        assert elapsed < 1.0

        release.set()
        time.sleep(0.05)
        mock_client.close.assert_called_once()

    def test_factory_returning_none_raises_clear_error(self):
        from consolidation_memory.runtime import MemoryRuntime

        runtime = MemoryRuntime(client_factory=lambda: None)
        runtime.startup()
        try:
            with pytest.raises(RuntimeError, match="factory returned None"):
                runtime.get_client(wait_timeout=0.5)
        finally:
            runtime.shutdown()

    def test_run_blocking_timeout_recycles_executor_capacity(self):
        from consolidation_memory.runtime import MemoryRuntime

        started = threading.Event()
        release = threading.Event()
        runtime = MemoryRuntime(max_workers=1)
        runtime.startup()

        def _slow_call():
            started.set()
            release.wait(timeout=5.0)
            return "slow"

        try:
            with pytest.raises(TimeoutError):
                asyncio.run(runtime.run_blocking(_slow_call, timeout=0.05))
            assert started.wait(timeout=1.0)

            before = time.monotonic()
            result = asyncio.run(runtime.run_blocking(lambda: "fast", timeout=0.3))
            elapsed = time.monotonic() - before

            assert result == "fast"
            assert elapsed < 0.2
        finally:
            release.set()
            runtime.shutdown()
