"""What may appear in the sidebar's chat tree, and what may not.

The sidebar narrows on `Chat.kind`, and passes `""` whenever the Chat/Agent
switch is absent -- which is every instance with agent chats turned off. For as
long as there were exactly two kinds, "" meaning "no filter" and "" meaning
"both sides of the switch" were the same thing. They stopped being the same
thing the moment a third kind existed, and the difference is invisible until
somebody has a conversation that belongs to a section instead of to the tree.

That is the shape this file exists for: correct code whose meaning changed
underneath it. It is pinned in both places that do the narrowing, because they
are two implementations of one rule and only one of them is SQL.
"""

from __future__ import annotations

from fastapi.testclient import TestClient
from sqlalchemy import select

from lembas.db.models import ALL_KINDS, KIND_MESSAGES, KIND_TASK, KINDS, Chat, Folder, User


def _user(db) -> User:
    return db.scalars(select(User).order_by(User.created_at)).first()


def _chat(db, *, kind: str, folder: Folder | None = None) -> Chat:
    chat = Chat(user_id=_user(db).id, kind=kind, folder_id=folder.id if folder else None)
    db.add(chat)
    db.commit()
    return chat


# --- The vocabulary -----------------------------------------------------------
def test_kinds_stays_the_two_sided_switch():
    """`api/preferences.py:set_sidebar_kind` validates against KINDS, so a third
    entry makes the tree filterable to a side with no button to leave it -- the
    "one side of a fork nobody can move" failure `sidebar_split` already guards.
    New kinds go in ALL_KINDS.
    """
    assert KINDS == ("chat", "agent")
    assert set(ALL_KINDS) > set(KINDS)


def test_the_sidebar_switch_refuses_a_kind_that_is_not_a_side(
    client: TestClient, db, registered
):
    client.post("/api/preferences/sidebar-kind", data={"kind": KIND_TASK})
    stored = (_user(db).settings_json or {}).get("sidebar_kind")
    assert stored != KIND_TASK


# --- The two narrowings -------------------------------------------------------
def test_unfiled_sections_chats_stay_out_of_the_tree(client: TestClient, db, registered):
    """With no switch on screen the sidebar asks for "" -- and "" must not mean
    "everything". This is the SQL half, in `sidebar_context`."""
    from lembas.api.pages import sidebar_context

    ordinary = _chat(db, kind="chat")
    task = _chat(db, kind=KIND_TASK)
    conversation = _chat(db, kind=KIND_MESSAGES)

    listed = {c.id for c in sidebar_context(db, _user(db))["unfiled_chats"]}
    assert ordinary.id in listed
    assert task.id not in listed
    assert conversation.id not in listed


def test_foldered_sections_chats_stay_out_of_the_tree(client: TestClient, db, registered):
    """And this is the Python half, in `Folder.visible_chats`. Two
    implementations of one rule, so both are pinned: fixing only the query would
    leave a task chat filed in a folder showing up anyway.
    """
    folder = Folder(user_id=_user(db).id, name="Work")
    db.add(folder)
    db.commit()

    ordinary = _chat(db, kind="chat", folder=folder)
    _chat(db, kind=KIND_TASK, folder=folder)

    listed = {c.id for c in folder.visible_chats()}
    assert listed == {ordinary.id}
    # And with the switch present it is still only the ordinary one.
    assert {c.id for c in folder.visible_chats("chat")} == {ordinary.id}


def test_a_folder_holding_only_a_task_chat_reads_as_empty(client: TestClient, db, registered):
    """`shown_in` keeps a folder that is empty of everything, because hiding a
    container somebody just made means it can never be filed into. A folder
    holding only a task chat has to count as that empty one -- otherwise it
    shows on both sides claiming contents nobody can see.
    """
    folder = Folder(user_id=_user(db).id, name="Scheduled work")
    db.add(folder)
    db.commit()
    _chat(db, kind=KIND_TASK, folder=folder)

    assert folder.holds() is False
    assert folder.shown_in("chat") is True


def test_the_composer_cannot_manufacture_a_section_chat(client: TestClient, db, registered):
    """`_new_chat` collapses kind to agent-or-chat, so this is already true by
    construction. Pinned so it stays true: the ordinary composer is a form
    anybody can post to."""
    from lembas.db.models import Connection, Model

    connection = Connection(name="local", base_url="http://x.test/v1", enabled=True)
    db.add(connection)
    db.commit()
    db.add(Model(connection_id=connection.id, model_id="m", display_name="M", enabled=True))
    db.commit()

    client.post("/api/chats/start", data={"content": "hello", "kind": KIND_TASK})
    kinds = {c.kind for c in db.scalars(select(Chat))}
    assert KIND_TASK not in kinds
