"""Tests for SessionManager behaviour after first-class field promotion."""

from __future__ import annotations

import pytest

from agentbyte.context import AgentContext
from agentbyte.session_store import InMemorySessionStore

try:
    from agentbyte.webui.sessions import SessionBindingError, SessionManager
    _WEBUI_AVAILABLE = True
except ImportError:  # pragma: no cover
    _WEBUI_AVAILABLE = False

pytestmark = pytest.mark.skipif(not _WEBUI_AVAILABLE, reason="webui extra not installed")


@pytest.fixture
def manager() -> SessionManager:
    return SessionManager(store=InMemorySessionStore())


# ---------------------------------------------------------------------------
# get_or_create — metadata should not contain timestamps
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_get_or_create_does_not_write_timestamps_to_metadata(manager) -> None:
    ctx = await manager.get_or_create("sess-1", "agent-1", "agent")
    assert "created_at" not in ctx.metadata
    assert "last_activity" not in ctx.metadata


@pytest.mark.asyncio
async def test_get_or_create_preserves_entity_metadata(manager) -> None:
    ctx = await manager.get_or_create("sess-2", "my-agent", "orchestrator")
    assert ctx.metadata["entity_id"] == "my-agent"
    assert ctx.metadata["entity_type"] == "orchestrator"


# ---------------------------------------------------------------------------
# update — metadata should not receive last_activity
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_update_does_not_write_last_activity_to_metadata(manager) -> None:
    ctx = await manager.get_or_create("sess-3", "agent-1", "agent")
    await manager.update("sess-3", ctx)
    assert "last_activity" not in ctx.metadata


@pytest.mark.asyncio
async def test_update_stamps_updated_at_via_store(manager) -> None:
    ctx = await manager.get_or_create("sess-4", "agent-1", "agent")
    await manager.update("sess-4", ctx)

    loaded = await manager.get("sess-4")
    assert loaded is not None
    assert loaded.updated_at is not None


# ---------------------------------------------------------------------------
# list — uses first-class fields, not metadata strings
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_list_last_activity_equals_updated_at(manager) -> None:
    ctx = await manager.get_or_create("sess-5", "agent-1", "agent")
    await manager.update("sess-5", ctx)

    sessions = await manager.list()
    assert len(sessions) == 1
    loaded = await manager.get("sess-5")
    assert sessions[0].last_activity == loaded.updated_at


@pytest.mark.asyncio
async def test_list_falls_back_to_created_at_when_updated_at_is_none(manager) -> None:
    """A context inserted with updated_at=None should use created_at as last_activity."""
    ctx = AgentContext(
        session_id="sess-6",
        metadata={"entity_id": "agent-1", "entity_type": "agent"},
        updated_at=None,
    )
    # Bypass the store's save() stamping by inserting into the backing dict directly
    manager.store._store["sess-6"] = ctx.model_copy(deep=True)

    sessions = await manager.list()
    match = next(s for s in sessions if s.id == "sess-6")
    assert match.last_activity == ctx.created_at


@pytest.mark.asyncio
async def test_list_includes_user_id_in_session_info(manager) -> None:
    ctx = await manager.get_or_create("sess-7", "agent-1", "agent")
    ctx.user_id = "user-99"
    await manager.update("sess-7", ctx)

    sessions = await manager.list()
    assert sessions[0].user_id == "user-99"


@pytest.mark.asyncio
async def test_list_can_filter_by_user_id(manager) -> None:
    await manager.get_or_create("sess-8", "agent-1", "agent", user_id="alice")
    await manager.get_or_create("sess-9", "agent-1", "agent", user_id="bob")

    sessions = await manager.list(user_id="alice")

    assert [session.id for session in sessions] == ["sess-8"]


@pytest.mark.asyncio
async def test_list_can_filter_by_user_id_and_entity_id(manager) -> None:
    await manager.get_or_create("sess-10", "agent-1", "agent", user_id="alice")
    await manager.get_or_create("sess-11", "agent-2", "agent", user_id="alice")
    await manager.get_or_create("sess-12", "agent-1", "agent", user_id="bob")

    sessions = await manager.list(entity_id="agent-1", user_id="alice")

    assert [session.id for session in sessions] == ["sess-10"]


@pytest.mark.asyncio
async def test_get_or_create_rejects_entity_mismatch(manager) -> None:
    await manager.get_or_create("sess-13", "agent-1", "agent", user_id="alice")

    with pytest.raises(SessionBindingError, match="bound to entity agent-1"):
        await manager.get_or_create("sess-13", "agent-2", "agent", user_id="alice")


@pytest.mark.asyncio
async def test_get_or_create_rejects_conflicting_user_id(manager) -> None:
    await manager.get_or_create("sess-14", "agent-1", "agent", user_id="alice")

    with pytest.raises(SessionBindingError, match="owned by user alice"):
        await manager.get_or_create("sess-14", "agent-1", "agent", user_id="bob")


@pytest.mark.asyncio
async def test_get_or_create_allows_claiming_anonymous_session(manager) -> None:
    created = await manager.get_or_create("sess-15", "agent-1", "agent")
    assert created.user_id is None

    claimed = await manager.get_or_create("sess-15", "agent-1", "agent", user_id="alice")
    assert claimed.user_id == "alice"

    loaded = await manager.get("sess-15")
    assert loaded is not None
    assert loaded.user_id == "alice"
