"""Tests for the synapse_instructions tool and the hidden dispatcher profile."""

import pytest

from navi.profiles import ALL_PROFILES
from navi.core.registry import ProfileRegistry
from navi.tools import SynapseInstructionsTool
from navi.tools._internal.base import ToolContext


def _tool_with_store(monkeypatch, store):
    async def fake_get(self, user_id):
        return store

    monkeypatch.setattr(
        "navi.synapse.settings_store.SynapseSettingsStore.get",
        fake_get,
    )
    updates = []

    async def fake_save_instructions(self, user_id, content, edited_by, reason=None):
        updates.append((user_id, content, edited_by, reason))
        store.instructions = content

    monkeypatch.setattr(
        "navi.synapse.settings_store.SynapseSettingsStore.save_instructions",
        fake_save_instructions,
    )
    return SynapseInstructionsTool(pool_provider=lambda: None), updates


async def test_read_empty(monkeypatch):
    from navi.synapse.settings_store import SynapseSettings

    tool, _ = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
    ctx = ToolContext(session_id="s1", user_id="u1")
    result = await tool.execute({"op": "read"}, ctx=ctx)
    assert result.success
    assert "empty" in result.output.lower()


async def test_update_records_navi_edit(monkeypatch):
    from navi.synapse.settings_store import SynapseSettings

    tool, updates = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
    ctx = ToolContext(session_id="s1", user_id="u1")
    result = await tool.execute(
        {"op": "update", "content": "gntodo → developer", "reason": " clarified scope"},
        ctx=ctx,
    )
    assert result.success
    assert updates == [("u1", "gntodo → developer", "navi", "clarified scope")]


async def test_update_requires_content(monkeypatch):
    from navi.synapse.settings_store import SynapseSettings

    tool, updates = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
    ctx = ToolContext(session_id="s1", user_id="u1")
    result = await tool.execute({"op": "update"}, ctx=ctx)
    assert not result.success
    assert updates == []


async def test_no_user_context_fails(monkeypatch):
    from navi.synapse.settings_store import SynapseSettings

    tool, _ = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
    result = await tool.execute({"op": "read"})
    assert not result.success
    assert "user" in result.error.lower()


def test_dispatcher_profile_is_hidden():
    """The dispatcher profile exists, is hidden, and the registry serves it."""
    ids = [p.id for p in ALL_PROFILES]
    assert "dispatcher" in ids
    assert "secretary" in ids

    reg = ProfileRegistry()
    for p in ALL_PROFILES:
        reg.register(p)
    dispatcher = reg.get("dispatcher")
    assert dispatcher.is_hidden is True

    listed = [p.id for p in reg.all() if not getattr(p, "is_hidden", False)]
    assert "dispatcher" not in listed


# ── real pool wiring ─────────────────────────────────────────────────────────

def _settings_row(conn, **overrides):
    from tests.conftest_factory import FakeRecord

    row = dict(
        user_id="u1", reactions_enabled=True, push_target="app",
        completion_notify="important", instructions="react to mentions",
        updated_at=None,
    )
    row.update(overrides)
    conn.enqueue(FakeRecord(**row))
    return conn


async def test_read_goes_through_the_real_pool(monkeypatch):
    """Regression: the registry wires `pool_provider` to PgSessionStore._get_pool,
    which is async — the tool used to pass that coroutine into
    SynapseSettingsStore and die with
    "'coroutine' object has no attribute 'fetchrow'".

    SynapseSettingsStore is not mocked here: the document comes from the fake
    asyncpg connection.
    """
    import inspect

    from tests.conftest_factory import FakeConnection, FakePool

    from navi.tools import SynapseInstructionsTool

    conn = _settings_row(FakeConnection())
    pool = FakePool(conn)

    async def provider():  # what navi/core/registry.py passes: a bound _get_pool
        return pool

    result = await SynapseInstructionsTool(pool_provider=provider).execute(
        {"op": "read"}, ctx=ToolContext(session_id="s1", user_id="u1")
    )

    assert result.success, result.error
    assert result.output == "react to mentions"
    assert [c[0] for c in conn.calls] == ["fetchrow"]


async def test_sync_pool_provider_still_accepted(monkeypatch):
    """A provider that hands back an already-resolved pool keeps working."""
    from tests.conftest_factory import FakeConnection, FakePool

    from navi.tools import SynapseInstructionsTool

    conn = _settings_row(FakeConnection(), instructions="watch gntodo")
    pool = FakePool(conn)

    result = await SynapseInstructionsTool(pool_provider=lambda: pool).execute(
        {"op": "read"}, ctx=ToolContext(session_id="s1", user_id="u1")
    )

    assert result.success, result.error
    assert result.output == "watch gntodo"