"""DSE Embedding-Recall — deterministische semantische Schicht (gecacht).

Testet die reine Logik OHNE Embedding-Service: Cache-Treffer-Pfad,
Schwellen-Filter, Kandidaten-Schnitt, Reachability-Guard. Das Einbetten selbst
(Embedding-Service) ist Integration und wird auf macmini/Prod validiert.
"""

from __future__ import annotations

import asyncio
import json

import compliance.services.specialist_agents.dse._embedding_recall as er


_TEXT = ("Datenschutzerklaerung der Muster GmbH. " * 20)  # > 100 Zeichen


def _seed_cache(tmp_path, scores: dict[str, float]) -> str:
    p = tmp_path / "dse_embed_cache.json"
    p.write_text(json.dumps({er._doc_hash(_TEXT): scores}))
    return str(p)


def test_doc_hash_deterministic():
    # feste Funktion: gleicher Text → gleicher Hash (Reproduzierbarkeit)
    assert er._doc_hash(_TEXT) == er._doc_hash(_TEXT)
    assert er._doc_hash("a") != er._doc_hash("b")


def test_cache_hit_threshold_filter(tmp_path, monkeypatch):
    # Cache-Treffer: kein Embedding-Service nötig. Nur Scores >= Schwelle UND
    # in den Kandidaten werden zurückgegeben.
    scores = {"DATA-1": 0.71, "DATA-2": 0.60, "AUTH-3": 0.68, "SEC-4": 0.50}
    monkeypatch.setenv("DSE_EMBED_CACHE", _seed_cache(tmp_path, scores))
    monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "dse_embed_cache.json"))

    cands = ["DATA-1", "DATA-2", "AUTH-3", "SEC-4"]
    out = asyncio.run(er.embedding_recall(_TEXT, cands, threshold=0.65))
    # >=0.65: DATA-1 (0.71), AUTH-3 (0.68). NICHT DATA-2 (0.60), SEC-4 (0.50).
    assert out == {"DATA-1", "AUTH-3"}


def test_cache_hit_candidate_intersection(tmp_path, monkeypatch):
    # Nur Kandidaten (durchgefallene Controls) zählen — andere ignoriert.
    scores = {"DATA-1": 0.90, "DATA-2": 0.90}
    monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "c.json"))
    (tmp_path / "c.json").write_text(json.dumps({er._doc_hash(_TEXT): scores}))
    out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"], threshold=0.65))
    assert out == {"DATA-1"}  # DATA-2 nicht in Kandidaten


def test_empty_inputs():
    assert asyncio.run(er.embedding_recall("zu kurz", ["X"])) == set()
    assert asyncio.run(er.embedding_recall(_TEXT, [])) == set()


def test_service_down_returns_empty(tmp_path, monkeypatch):
    # Kein Cache + Service nicht erreichbar → leer (deterministischer Layer trägt),
    # KEIN Hang.
    monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "none.json"))

    async def _unreachable(timeout=2.0):
        return False
    monkeypatch.setattr(er, "_embedding_reachable", _unreachable)
    out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"]))
    assert out == set()
