"""ASV benchmarks for session list service-layer performance baseline (bug #554).

Measures the cost of listing sessions through ``PersistentSessionService``
using a file-based SQLite database.  These benchmarks construct the service
directly (bypassing DI wiring) and therefore do **not** exercise the
``_get_session_service()`` code path that triggers bug #554.  Their purpose
is to establish a performance baseline for the service layer itself.
"""

from __future__ import annotations

import os
import shutil
import sys
import tempfile
from pathlib import Path

_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from sqlalchemy import create_engine  # noqa: E402
from sqlalchemy.orm import sessionmaker  # noqa: E402

from cleveragents.application.services.session_service import (  # noqa: E402
    PersistentSessionService,
)
from cleveragents.infrastructure.database.models import Base  # noqa: E402
from cleveragents.infrastructure.database.repositories import (  # noqa: E402
    SessionMessageRepository,
    SessionRepository,
)


class SessionListDISuite:
    """Benchmark session list through the service layer (direct construction).

    Engine and sessionmaker are built once in ``setup()`` and reused across
    ``time_list_empty`` iterations.  Methods that mutate state
    (``time_list_after_create``, ``track_list_after_create_count``) use
    per-method setup/teardown to get a fresh database each time, preventing
    row accumulation across ASV iterations.

    Does **not** exercise ``_get_session_service()`` or the DI container
    wiring.
    """

    timeout = 30.0

    # -- suite-level setup (shared engine for read-only benchmarks) ----------

    def setup(self) -> None:
        self._tmpdir = tempfile.mkdtemp(prefix="bench_sle_554_")
        self._db_path = os.path.join(self._tmpdir, "bench.db")
        self._engine = create_engine(
            f"sqlite:///{self._db_path}",
            echo=False,
        )
        Base.metadata.create_all(self._engine)
        self._session_factory = sessionmaker(
            bind=self._engine,
            expire_on_commit=False,
        )
        # Pre-initialise attributes used by per-method setup hooks so that
        # benchmarks still work even when the ASV runner skips per-method
        # setup_<name>() callbacks (e.g. in fork-server mode).
        self._empty_svc = self._make_service()
        self._fresh_engine()

    def teardown(self) -> None:
        self._engine.dispose()
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def _make_service(self) -> PersistentSessionService:
        """Build a PersistentSessionService using the shared session factory."""
        return PersistentSessionService(
            session_repo=SessionRepository(
                session_factory=self._session_factory,
            ),
            message_repo=SessionMessageRepository(
                session_factory=self._session_factory,
            ),
        )

    # -- per-method setup for mutating benchmarks ----------------------------

    def _fresh_engine(self) -> None:
        """Create an isolated engine+factory so create() doesn't accumulate."""
        self._mut_tmpdir = tempfile.mkdtemp(prefix="bench_sle_554_mut_")
        db_path = os.path.join(self._mut_tmpdir, "bench.db")
        self._mut_engine = create_engine(
            f"sqlite:///{db_path}",
            echo=False,
        )
        Base.metadata.create_all(self._mut_engine)
        self._mut_factory = sessionmaker(
            bind=self._mut_engine,
            expire_on_commit=False,
        )

    def _dispose_fresh(self) -> None:
        if hasattr(self, "_mut_engine"):
            self._mut_engine.dispose()
        if hasattr(self, "_mut_tmpdir"):
            shutil.rmtree(self._mut_tmpdir, ignore_errors=True)

    def _make_mut_service(self) -> PersistentSessionService:
        return PersistentSessionService(
            session_repo=SessionRepository(session_factory=self._mut_factory),
            message_repo=SessionMessageRepository(
                session_factory=self._mut_factory,
            ),
        )

    # -- ASV per-method hooks ------------------------------------------------

    def setup_time_list_after_create(self) -> None:
        self._fresh_engine()

    def teardown_time_list_after_create(self) -> None:
        self._dispose_fresh()

    def setup_track_list_after_create_count(self) -> None:
        self._fresh_engine()

    def teardown_track_list_after_create_count(self) -> None:
        self._dispose_fresh()

    def setup_time_list_empty(self) -> None:
        self._empty_svc = self._make_service()

    # -- benchmarks ----------------------------------------------------------

    def time_list_empty(self) -> None:
        """List sessions when DB is empty (service-layer only, no DI)."""
        self._empty_svc.list()

    def time_list_after_create(self) -> None:
        """Create then list (service-layer only, fresh DB per iteration).

        Bypasses ``_get_session_service()`` / DI container — measures the
        service-layer round-trip cost, not the DI wiring path.
        """
        svc = self._make_mut_service()
        svc.create()
        svc.list()

    def track_list_after_create_count(self) -> int:
        """Track session list persistence at the service layer.

        Returns the count of sessions visible after a create.  Uses a
        fresh DB per iteration so the count is always exactly 1.
        This benchmark constructs ``PersistentSessionService`` directly,
        bypassing the DI container — it does **not** reproduce bug #554.
        """
        svc = self._make_mut_service()
        svc.create(actor_name="bench/test")
        sessions = svc.list()
        return len(sessions)


SessionListDISuite.track_list_after_create_count.unit = "sessions"
