"""
tests/test_stepan_v42_vision_bridge.py

Unit tests for v4.2 Vision → Agronomy Bridge.

Тестуємо ізольовано: без crewai, без httpx, без memory-service.
Перевіряємо логіку безпосередньо з vision_guard + session_context.
"""
from __future__ import annotations
import time
import sys
import os
import pytest

# ── Налаштовуємо шляхи для локального запуску ────────────────────────────────
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_GATEWAY = os.path.join(_ROOT, "gateway-bot")
_CREWS = os.path.join(_ROOT, "crews")
if _GATEWAY not in sys.path:
    sys.path.insert(0, _GATEWAY)
if _CREWS not in sys.path:
    sys.path.insert(0, _CREWS)

import vision_guard as vg


# ── Fixtures ──────────────────────────────────────────────────────────────────

@pytest.fixture(autouse=True)
def clear_vg_lock():
    vg._VISION_LOCK.clear()
    yield
    vg._VISION_LOCK.clear()


def _make_session() -> dict:
    """Мінімальна сесія для тестів (без load_session)."""
    return {
        "doc_focus": False,
        "doc_focus_ts": 0.0,
        "doc_focus_cooldown_until": 0.0,
        "active_doc_id": None,
        "doc_facts": {},
        "fact_claims": [],
        "last_photo_ts": 0.0,
        "farm_state": {},
        "vision_last_label": "",
        "updated_at": time.time(),
    }


# ── Helper: симулює логіку v4.2 з run.py ─────────────────────────────────────

def _run_vision_bridge(
    session: dict,
    agent_id: str,
    chat_id: str,
    text: str = "",
) -> dict:
    """
    Відтворює блок v4.2 з run.py (без crewai/LLM).
    Повертає оновлену session.
    """
    # Читаємо vision lock (аналог _vb_get_vision_lock)
    try:
        lock = vg.get_vision_lock(agent_id, chat_id)
        if lock:
            label = (lock.get("user_label") or lock.get("label") or "").strip()
            if label:
                session["vision_last_label"] = label
    except Exception:
        pass

    # User text override
    if text:
        try:
            text_label = vg.detect_user_override(text)
            if text_label:
                session["vision_last_label"] = text_label
        except Exception:
            pass

    return session


def _should_inject_prefix(session: dict, context_mode: str, domain: str) -> bool:
    """Відтворює умову для vision bridge prefix injection."""
    if context_mode == "doc":
        return False
    if domain == "web":
        return False
    label = (session.get("vision_last_label") or "").strip()
    farm_crop = str((session.get("farm_state") or {}).get("current_crop", "")).strip()
    return bool(label and label != farm_crop)


# ─── Тест 1: vision label з'являється у session після lock ───────────────────

class TestVisionLabelInSession:
    def test_label_loaded_from_lock(self):
        """Якщо vision lock є — vision_last_label заповнюється."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "соняшник", "high",
                           file_unique_id="uid1")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        assert session["vision_last_label"] == "соняшник"

    def test_user_label_preferred_over_model_label(self):
        """user_label має пріоритет над model label."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "пшениця", "high",
                           file_unique_id="uid1")
        vg.set_user_label("agromatrix", "chat1", "Соняшник")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        assert session["vision_last_label"] == "соняшник"

    def test_no_label_if_no_lock(self):
        """Без lock — vision_last_label порожній."""
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat_new")
        assert session["vision_last_label"] == ""

    def test_expired_lock_no_label(self):
        """Протухлий lock — не вставляємо label."""
        vg.set_vision_lock("agromatrix", "chatX", "fid", "кукурудза", "high")
        vg._VISION_LOCK["agromatrix:chatX"]["ts"] = time.time() - vg.VISION_LOCK_TTL - 1
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chatX")
        assert session["vision_last_label"] == ""


# ─── Тест 2: prefix injection тільки не в doc/web режимі ─────────────────────

class TestPrefixInjection:
    def test_injected_in_general_mode(self):
        vg.set_vision_lock("agromatrix", "chat1", "fid", "соняшник", "high")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        assert _should_inject_prefix(session, "general", "general") is True

    def test_not_injected_in_doc_mode(self):
        """Rule: doc mode → NЕ вставляємо vision prefix."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "кукурудза", "high")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        assert _should_inject_prefix(session, "doc", "general") is False

    def test_not_injected_in_web_mode(self):
        """Rule: web mode → НЕ вставляємо vision prefix."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "пшениця", "high")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        assert _should_inject_prefix(session, "general", "web") is False

    def test_not_injected_if_empty_label(self):
        """Без label — не вставляємо."""
        session = _make_session()
        assert _should_inject_prefix(session, "general", "general") is False

    def test_not_injected_if_same_as_farm_crop(self):
        """Якщо farm_state.current_crop === vision_last_label — не дублюємо."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "кукурудза", "high")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat1")
        session["farm_state"] = {"current_crop": "кукурудза"}
        assert _should_inject_prefix(session, "general", "general") is False


# ─── Тест 3: user text override ──────────────────────────────────────────────

class TestTextOverride:
    def test_text_override_changes_label(self):
        """Юзер явно пише "це соняшник" → перезаписує label."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "пшениця", "high")
        session = _make_session()
        session["vision_last_label"] = "пшениця"
        # Симулюємо нову відправку з override text
        session = _run_vision_bridge(session, "agromatrix", "chat1", text="це соняшник")
        assert session["vision_last_label"] == "соняшник"

    def test_negation_does_not_override(self):
        """Негація "це не соняшник" → не перезаписує."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "пшениця", "high")
        session = _make_session()
        session["vision_last_label"] = "пшениця"
        session = _run_vision_bridge(session, "agromatrix", "chat1", text="це не соняшник")
        assert session["vision_last_label"] == "пшениця"

    def test_irrelevant_text_no_change(self):
        """Звичайний текст → label не змінюється."""
        vg.set_vision_lock("agromatrix", "chat1", "fid", "ріпак", "high")
        session = _make_session()
        session["vision_last_label"] = "ріпак"
        session = _run_vision_bridge(session, "agromatrix", "chat1",
                                     text="чи варто внести азот зараз?")
        assert session["vision_last_label"] == "ріпак"

    def test_text_override_sets_label_without_lock(self):
        """User пише "соняшник" навіть без попереднього lock."""
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat_no_lock",
                                     text="це кукурудза")
        assert session["vision_last_label"] == "кукурудза"


# ─── Тест 4: TTL — label зникає після 30 хвилин ──────────────────────────────

class TestTTL:
    def test_label_absent_after_ttl(self):
        """Після TTL lock→ порожній → label не завантажується."""
        vg.set_vision_lock("agromatrix", "chatTTL", "fid", "ячмінь", "high")
        # Симулюємо протухання
        vg._VISION_LOCK["agromatrix:chatTTL"]["ts"] = time.time() - vg.VISION_LOCK_TTL - 1
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chatTTL")
        assert session["vision_last_label"] == ""

    def test_label_present_within_ttl(self):
        """Якщо lock свіжий — label є."""
        vg.set_vision_lock("agromatrix", "chatTTL2", "fid", "горох", "high")
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chatTTL2")
        assert session["vision_last_label"] == "горох"


# ─── Тест 5: ізоляція між чатами ─────────────────────────────────────────────

class TestChatIsolation:
    def test_different_chats_isolated(self):
        """Два різні чати — окремі label."""
        vg.set_vision_lock("agromatrix", "chatA", "f1", "соняшник", "high")
        vg.set_vision_lock("agromatrix", "chatB", "f2", "пшениця", "high")

        sA = _make_session()
        sA = _run_vision_bridge(sA, "agromatrix", "chatA")
        sB = _make_session()
        sB = _run_vision_bridge(sB, "agromatrix", "chatB")

        assert sA["vision_last_label"] == "соняшник"
        assert sB["vision_last_label"] == "пшениця"

    def test_forward_different_chat_no_bleed(self):
        """Forward фото в інший чат — чужий lock не потрапляє."""
        vg.set_vision_lock("agromatrix", "chatSource", "f1", "ріпак", "high",
                           file_unique_id="uid_shared")
        # В іншому чаті немає lock навіть з тим самим photo
        sB = _make_session()
        sB = _run_vision_bridge(sB, "agromatrix", "chatDest")
        assert sB["vision_last_label"] == ""


# ─── Тест 6: Smoke cycle ─────────────────────────────────────────────────────

class TestSmokeCycle:
    def test_full_vision_agronomy_flow(self):
        """
        1. Фото → lock_set (соняшник)
        2. Степан отримує label у session
        3. Питання без фото → prefix інжектується
        4. Юзер каже "тепер це кукурудза" → label змінюється
        5. TTL минає → label пропадає
        """
        # 1. Vision response saved
        vg.set_vision_lock("agromatrix", "chat_cycle", "fid",
                           "соняшник", "high", file_unique_id="uid_cycle")

        # 2. run.py bridge
        session = _make_session()
        session = _run_vision_bridge(session, "agromatrix", "chat_cycle")
        assert session["vision_last_label"] == "соняшник"

        # 3. Inject в general mode
        assert _should_inject_prefix(session, "general", "general") is True
        assert _should_inject_prefix(session, "doc", "general") is False

        # 4. Text override
        session = _run_vision_bridge(session, "agromatrix", "chat_cycle",
                                     text="тепер це кукурудза")
        assert session["vision_last_label"] == "кукурудза"

        # 5. TTL
        vg._VISION_LOCK["agromatrix:chat_cycle"]["ts"] = (
            time.time() - vg.VISION_LOCK_TTL - 1
        )
        session2 = _make_session()
        session2 = _run_vision_bridge(session2, "agromatrix", "chat_cycle")
        assert session2["vision_last_label"] == ""
