"""Tests for the saveMessages knob: when false, the provider never writes to Honcho.

The knob has always been parsed by HonchoClientConfig but was not consumed by
the write paths (sync_turn / on_memory_write / on_session_end). These tests pin
the contract: saveMessages=false disables all automatic persistence while read
and tools paths remain untouched.
"""

from unittest.mock import MagicMock

from plugins.memory.honcho import HonchoMemoryProvider
from plugins.memory.honcho.client import HonchoClientConfig


def _provider(save_messages: bool) -> HonchoMemoryProvider:
    p = HonchoMemoryProvider()
    p._config = HonchoClientConfig(save_messages=save_messages)
    p._manager = MagicMock()
    p._session_key = 'test-session'
    p._session_initialized = True
    return p


class TestSyncTurn:
    def test_disabled_writes_nothing(self):
        p = _provider(save_messages=False)
        p.sync_turn('user says', 'assistant says')
        p._manager.get_or_create.assert_not_called()
        p._manager.save.assert_not_called()

    def test_enabled_routes_through_save(self):
        p = _provider(save_messages=True)
        p.sync_turn('user says', 'assistant says')
        if p._sync_thread is not None:
            p._sync_thread.join(timeout=5)
        p._manager.get_or_create.assert_called_once()
        # save() (not _flush_session) so writeFrequency batching is honored
        p._manager.save.assert_called_once()

    def test_enabled_writes(self):
        p = _provider(save_messages=True)
        p.sync_turn('user says', 'assistant says')
        if p._sync_thread is not None:
            p._sync_thread.join(timeout=5)
        p._manager.get_or_create.assert_called_once()


class TestOnMemoryWrite:
    def test_disabled_skips_conclusion_mirror(self):
        p = _provider(save_messages=False)
        p.on_memory_write('add', 'user', 'user likes coffee')
        p._manager.create_conclusion.assert_not_called()

    def test_enabled_mirrors(self):
        import time

        p = _provider(save_messages=True)
        p.on_memory_write('add', 'user', 'user likes coffee')
        deadline = time.time() + 5
        while time.time() < deadline and not p._manager.create_conclusion.called:
            time.sleep(0.05)
        p._manager.create_conclusion.assert_called_once()


class TestOnSessionEnd:
    def test_disabled_skips_flush(self):
        p = _provider(save_messages=False)
        p.on_session_end([])
        p._manager.flush_all.assert_not_called()

    def test_enabled_flushes(self):
        p = _provider(save_messages=True)
        p.on_session_end([])
        p._manager.flush_all.assert_called_once()


class TestShutdown:
    """shutdown() joins worker threads then delegates to the session manager:
    manager.shutdown() (flush + join async writer) when persistence is on,
    manager.stop_async_writer() (join only, no flush) when saveMessages=false.
    Cleanup runs in both cases; only persistence is gated."""

    def _provider_for_shutdown(self, save_messages: bool) -> HonchoMemoryProvider:
        p = _provider(save_messages=save_messages)
        # shutdown() iterates these thread handles; if no turn/session-end ran
        # they may be unset, so default to None (= "no thread started").
        p._init_thread = None
        p._prefetch_thread = None
        p._sync_thread = None
        return p

    def test_disabled_skips_flush_but_stops_writer(self):
        p = self._provider_for_shutdown(save_messages=False)
        p.shutdown()
        p._manager.flush_all.assert_not_called()
        p._manager.shutdown.assert_not_called()
        p._manager.stop_async_writer.assert_called_once()

    def test_enabled_shuts_down_manager(self):
        p = self._provider_for_shutdown(save_messages=True)
        p.shutdown()
        # manager.shutdown() flushes AND joins the async-writer thread;
        # calling flush_all() alone left the writer thread alive at exit.
        p._manager.shutdown.assert_called_once()
