"""Regression test for background jobs — deterministic ordering.

After Part 4 of the fact-query-abstraction refactor, every full scan in
``src/arandu/background/`` uses ``FactQuery`` with stable ordering. This
prevents silent drift in clustering, consolidation, memify and other
jobs where the order of facts influences the output (e.g. which fact
becomes the cluster representative, which pattern gets detected first).

This test mocks the session to return a fixed list of facts with
identical timestamps — the scenario that would trigger non-determinism
if the builder did not embed ``MemoryFact.id`` as tiebreaker — and
confirms that two back-to-back runs of ``cluster_user_facts`` return
identical cluster outputs.

The test is intentionally lightweight: it exercises only the query
path, not the LLM-backed summary generation step.
"""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

import pytest

NOW = datetime(2026, 4, 9, 12, 0, 0, tzinfo=UTC)


def _make_fact(
    fact_id: uuid.UUID,
    entity_key: str,
    fact_text: str,
) -> MagicMock:
    """Build a MagicMock MemoryFact with shared timestamps."""
    mock = MagicMock()
    mock.id = fact_id
    mock.entity_key = entity_key
    mock.entity_type = "person"
    mock.entity_name = "Pedro"
    mock.fact_text = fact_text
    mock.confidence = 0.9
    mock.importance = 0.5
    mock.valid_from = NOW  # intentional: identical timestamps
    mock.valid_to = None
    mock.created_at = NOW
    mock.cluster_id = None
    mock.embedding_vec = [0.1] * 1536
    mock.attribute_key = None
    mock.value_json = None
    mock.value_text = None
    mock.category = None
    mock.speaker = "Pedro"
    mock.is_stale = False
    return mock


def _make_session_returning(facts: list[MagicMock]) -> AsyncMock:
    """AsyncSession mock that always returns the same facts."""
    session = AsyncMock()

    def _execute_side_effect(stmt, *args, **kwargs):  # type: ignore[no-untyped-def]
        result = MagicMock()
        result.scalars.return_value.all.return_value = facts
        result.all.return_value = [(f,) for f in facts]
        return result

    session.execute = AsyncMock(side_effect=_execute_side_effect)
    session.begin_nested = MagicMock()
    session.begin_nested.return_value.__aenter__ = AsyncMock()
    session.begin_nested.return_value.__aexit__ = AsyncMock(return_value=None)
    return session


class TestBackgroundDeterminism:
    """Full-scan background queries must produce identical outputs across runs."""

    @pytest.mark.asyncio
    async def test_fact_query_in_background_is_stable_across_runs(self) -> None:
        """Building the same FactQuery twice produces byte-identical SQL.

        This is the structural guarantee that powers deterministic
        background jobs: the builder is a pure function of its inputs,
        so two background runs against the same memory produce the
        same query, which in turn returns rows in the same order
        because of the ``MemoryFact.id`` tiebreaker embedded in every
        ordering method.
        """
        from sqlalchemy.dialects import postgresql

        from arandu._fact_query import FactQuery

        def build_sql() -> str:
            stmt = FactQuery.active("agent-1").with_deferred_embedding().order_by_recency().build()
            return str(stmt.compile(dialect=postgresql.dialect()))

        sql_1 = build_sql()
        sql_2 = build_sql()
        assert sql_1 == sql_2, "FactQuery output is not deterministic"
        # Confirm the stable tiebreaker is present
        assert "memory_facts.id" in sql_1

    @pytest.mark.asyncio
    async def test_order_by_importance_is_stable(self) -> None:
        """Profile consolidation (sleep_time) uses order_by_importance — also stable."""
        from sqlalchemy.dialects import postgresql

        from arandu._fact_query import FactQuery

        stmt = FactQuery.active("agent-1").for_entity("person:pedro").order_by_importance().limit(30).build()
        sql = str(stmt.compile(dialect=postgresql.dialect()))
        assert "ORDER BY memory_facts.importance DESC, memory_facts.id" in sql

    @pytest.mark.asyncio
    async def test_background_scan_respects_temporal_window(self) -> None:
        """clustering.cluster_user_facts uses within_temporal_window for age cutoff."""
        from sqlalchemy.dialects import postgresql

        from arandu._fact_query import FactQuery

        cutoff = datetime(2026, 3, 1, tzinfo=UTC)
        stmt = (
            FactQuery.active("agent-1")
            .within_temporal_window(cutoff, None)
            .with_deferred_embedding()
            .order_by_recency()
            .build()
        )
        sql = str(stmt.compile(dialect=postgresql.dialect()))
        assert "valid_from >=" in sql
        assert "memory_facts.id" in sql
