"""Chat session lifecycle tests.

Two tiers, same conventions as the other suites:

- Route tests (app_client, in-container): the empty-session lifecycle over
  real HTTP — create -> list -> end(discard). This is the tier that catches
  router<->service signature drift (the dev.10 bug: the router called
  end_session positionally; the service is keyword-only). The discard path
  never reaches the titling provider and deletes everything it created, so
  it leaves no litter in the migrated data.
- Service tests (db_session, in-container): the titled end path with an
  injected fake provider + session_factory, inside the savepoint that
  always rolls back.
"""
from __future__ import annotations

import asyncio
import os

import pytest

pytestmark = pytest.mark.skipif(
    not os.environ.get("DATABASE_URL"),
    reason="DATABASE_URL not set — chat DB tests run in-container",
)


# ---------------------------------------------------------------------------
# Route tier — empty-session lifecycle over HTTP (self-cleaning)
# ---------------------------------------------------------------------------

def test_empty_session_http_lifecycle(app_client, auth_headers):
    created = app_client.post("/api/chat/sessions", headers=auth_headers)
    assert created.status_code == 201, created.text
    sid = created.json()["id"]

    listed = app_client.get("/api/chat/sessions", headers=auth_headers)
    assert listed.status_code == 200
    assert any(s["id"] == sid for s in listed.json())

    replay = app_client.get(f"/api/chat/sessions/{sid}", headers=auth_headers)
    assert replay.status_code == 200
    assert replay.json()["messages"] == []

    ended = app_client.post(f"/api/chat/sessions/{sid}/end", headers=auth_headers)
    assert ended.status_code == 200, ended.text
    assert ended.json().get("discarded") is True

    # Discard removes the session (and its activity_log row) entirely.
    after = app_client.get("/api/chat/sessions", headers=auth_headers)
    assert all(s["id"] != sid for s in after.json())


def test_end_unknown_session_404(app_client, auth_headers):
    r = app_client.post("/api/chat/sessions/999999/end", headers=auth_headers)
    assert r.status_code == 404


# ---------------------------------------------------------------------------
# Service tier — titled end path (fake provider, savepoint rollback)
# ---------------------------------------------------------------------------

class _FakeProvider:
    async def complete(self, *, model: str, prompt: str, temperature: float = 0.0):
        return "Carrot Growth Check"


def test_end_session_titles_and_stamps(db_session):
    from datetime import datetime, timezone

    from app.models.assistant import ChatMessage
    from app.services.ai import chat as svc

    created = svc.create_session(db_session, garden_id=1)
    sid = created["id"]
    now = datetime.now(timezone.utc)
    db_session.add_all(
        [
            ChatMessage(
                session_id=sid, role="user",
                text="How are the carrots?", created_at=now,
            ),
            ChatMessage(
                session_id=sid, role="assistant",
                text="Growing slowly but healthy.", created_at=now,
            ),
        ]
    )
    db_session.flush()

    class _Factory:
        """Context-manager factory that hands out the savepoint session
        without closing it (end_session opens the factory twice)."""

        def __enter__(self):
            return db_session

        def __exit__(self, *exc):
            return False

    out = asyncio.run(
        svc.end_session(
            garden_id=1,
            session_id=sid,
            provider=_FakeProvider(),
            session_factory=_Factory,
        )
    )
    assert out["ok"] is True
    assert out["title"] == "Carrot Growth Check"
    assert "2 messages" in out["summary"]

    # Second end is a no-op, not an error (v1 semantics).
    again = asyncio.run(
        svc.end_session(
            garden_id=1,
            session_id=sid,
            provider=_FakeProvider(),
            session_factory=_Factory,
        )
    )
    assert again.get("already_ended") is True
