"""
Regression guard: service helpers must not close the caller's session.

`SessionLocal` is a thread-scoped session, so a nested `with session_scope()`
inside a service returns the SAME Session object as the caller's and then
closes it on exit — detaching every ORM object the caller still holds.

This is what broke /api/ai/write-reply with

    DetachedInstanceError: Parent instance <AddonsMessages> is not bound to a
    Session; lazy load operation of attribute 'thread' cannot proceed

`EmbeddingService.get_similar_threads()` opened such a nested scope during RAG
retrieval; by the time `_build_draft_prompt()` touched `message.thread` the
route's session was gone, the endpoint 500'd, and the UI closed the AI panel on
the failed `r.json()` — surfacing as "the AI result disappears once generated".

These tests need no OpenAI call: they exercise the session plumbing only.
"""

from __future__ import annotations

import pytest

from supporthub.app.db import session_scope, set_worker_tenant_schema
from supporthub.app.models import AddonsMessages
from supporthub.app.services.embedding_service import EmbeddingService, embedding_index

SCHEMA = "tenant_internal"


@pytest.fixture(scope="module", autouse=True)
def tenant_schema():
    set_worker_tenant_schema(SCHEMA)
    yield
    set_worker_tenant_schema(None)


def test_get_similar_threads_does_not_close_callers_session():
    """The caller's objects must still be attached after the RAG helper runs."""
    svc = EmbeddingService()
    if not embedding_index.loaded:
        embedding_index.load_from_db(schema=SCHEMA)
    if not embedding_index.loaded or embedding_index.count == 0:
        pytest.skip("embedding index unavailable")

    with session_scope(schema=SCHEMA) as session:
        msg = (
            session.query(AddonsMessages)
            .filter(AddonsMessages.direction == "inbound")
            .order_by(AddonsMessages.created_at.desc())
            .first()
        )
        if msg is None:
            pytest.skip("no inbound messages in this tenant")

        # Touch the relationship once so it is definitely loadable beforehand.
        assert msg.thread is not None

        vecs = embedding_index.snapshot()["thread_to_vec"]
        query_vec = next(iter(vecs.values()))

        svc.get_similar_threads(
            index=embedding_index,
            query_vec=query_vec,
            top_k=3,
            session=session,          # the contract under test
        )

        # The bug: the helper's nested session_scope() closed THIS session, so
        # the lazy load below raised DetachedInstanceError.
        assert msg.thread is not None, "caller's session was closed by the helper"
        assert session.query(AddonsMessages).count() > 0, "session no longer usable"


def test_get_similar_threads_still_works_without_a_session():
    """Callers that pass no session (eval scripts) keep the self-managed path."""
    svc = EmbeddingService()
    if not embedding_index.loaded:
        embedding_index.load_from_db(schema=SCHEMA)
    if not embedding_index.loaded or embedding_index.count == 0:
        pytest.skip("embedding index unavailable")

    query_vec = next(iter(embedding_index.snapshot()["thread_to_vec"].values()))
    result = svc.get_similar_threads(index=embedding_index, query_vec=query_vec, top_k=3)
    assert isinstance(result, list)
