"""
tests/test_stepan_doc_ux_v37.py

v3.7 Doc Focus UX Polish:
  - build_mode_clarifier per-domain (URL, vision, fact, neutral)
  - _DOC_AWARENESS_RE pattern matching
  - Context bleed guard uses clarifier (not static phrase)
  - state-aware doc ack ("Працюємо зі звітом." / "По звіту дивлюсь.")
  - Vision intro polish (_VISION_INTRO_RE)
  - "no inertia" — general mode should not produce doc phrases
"""
import re
import pytest

from crews.agromatrix_crew.doc_focus import (
    build_mode_clarifier,
    _VISION_INTRO_RE,
    _DOC_AWARENESS_RE,
)


# ─── 1. build_mode_clarifier (v3.7 контекстне уточнення) ─────────────────────

class TestBuildModeClarifierV37:
    """build_mode_clarifier виробляє 1 питання без "!" і без "будь ласка"."""

    def test_url_question(self):
        c = build_mode_clarifier("https://bayer.com/catalogue")
        assert "посилання" in c.lower()
        assert c.endswith("?")

    def test_vision_question(self):
        c = build_mode_clarifier("на листі жовті плями")
        assert "фото" in c.lower() or "рослин" in c.lower()
        assert c.endswith("?")

    def test_fact_question(self):
        c = build_mode_clarifier("що з витратами на добрива?")
        assert "цифри" in c.lower() or "звіт" in c.lower()
        assert c.endswith("?")

    def test_neutral_question(self):
        c = build_mode_clarifier("що там?")
        # Neutral fallback
        assert "звіт" in c.lower() or "інше" in c.lower()
        assert c.endswith("?")

    def test_no_bud_laska(self):
        for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
            c = build_mode_clarifier(text)
            assert "будь ласка" not in c.lower()
            assert "!" not in c

    def test_exactly_one_question_mark(self):
        for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
            c = build_mode_clarifier(text)
            assert c.count("?") == 1, f"Expected 1 '?', got '{c}'"

    def test_max_length(self):
        for text in ["https://x.com", "листя плями", "витрати?", "привіт"]:
            c = build_mode_clarifier(text)
            assert len(c) <= 60, f"Clarifier too long: '{c}'"


# ─── 2. _DOC_AWARENESS_RE — заборонені UX-фрази ─────────────────────────────

class TestDocAwarenessRegex:
    """v3.7: Регекс для виявлення "Так, пам'ятаю" / "Не бачу його" тощо."""

    BANNED_PHRASES = [
        "Так, пам'ятаю документ.",
        "Так, пам\u2019ятаю.",       # curly apostrophe
        "Не бачу його перед собою.",
        "Не бачу його сьогодні.",
        "Мені доступний документ.",
        "Мені не доступний документ.",
    ]

    ALLOWED_PHRASES = [
        "Працюємо зі звітом.",
        "По звіту дивлюсь.",
        "Йдеться про звіт чи про інше?",
    ]

    def test_banned_matched(self):
        for phrase in self.BANNED_PHRASES:
            assert _DOC_AWARENESS_RE.search(phrase), f"Should match banned: '{phrase}'"

    def test_allowed_not_matched(self):
        for phrase in self.ALLOWED_PHRASES:
            assert not _DOC_AWARENESS_RE.search(phrase), f"Should NOT match allowed: '{phrase}'"


# ─── 3. _VISION_INTRO_RE — "На фото видно" ───────────────────────────────────

class TestVisionIntroRegex:
    def test_matches_vision_intro(self):
        assert _VISION_INTRO_RE.search("На фото видно жовті плями.")

    def test_no_match_other(self):
        assert not _VISION_INTRO_RE.search("Схоже на хлороз листя.")
        assert not _VISION_INTRO_RE.search("Ймовірно брак заліза.")


# ─── 4. Context bleed guard — використовує clarifier (не статичну фразу) ─────

def _apply_bleed_guard_v37(response: str, text: str) -> tuple[str, bool]:
    """
    Симуляція v3.6/v3.7 bleed guard логіки (context_mode=general):
    замінює doc-фрази на build_mode_clarifier(text).
    """
    _BLEED_RE = re.compile(
        r"у\s+(?:цьому|наданому|даному)\s+документі"
        r"|в\s+(?:цьому|наданому|даному)\s+документі"
        r"|у\s+(?:цьому\s+)?звіті|в\s+(?:цьому\s+)?звіті",
        re.IGNORECASE | re.UNICODE,
    )
    if _BLEED_RE.search(response):
        return build_mode_clarifier(text), True
    return response, False


class TestBleedGuardUseClarifier:
    def test_doc_phrase_replaced_by_clarifier(self):
        resp = "У цьому документі вказано прибуток 1М грн."
        out, replaced = _apply_bleed_guard_v37(resp, "що там?")
        assert replaced
        assert "У цьому документі" not in out
        assert "?" in out

    def test_doc_phrase_url_context_clarifier(self):
        resp = "У звіті є дані."
        out, replaced = _apply_bleed_guard_v37(resp, "https://example.com")
        assert replaced
        assert "посилання" in out.lower()

    def test_doc_phrase_vision_context_clarifier(self):
        resp = "В цьому документі щось."
        out, replaced = _apply_bleed_guard_v37(resp, "на листі плями")
        assert replaced
        assert "фото" in out.lower() or "рослин" in out.lower()

    def test_clean_response_not_touched(self):
        resp = "Добрива коштують 2000 грн/га."
        out, replaced = _apply_bleed_guard_v37(resp, "скільки добрив?")
        assert not replaced
        assert out == resp

    def test_case_insensitive(self):
        resp = "У ЦЬОМУ ДОКУМЕНТІ нічого немає."
        out, replaced = _apply_bleed_guard_v37(resp, "що там")
        assert replaced


# ─── 5. State-aware doc ack симуляція ─────────────────────────────────────────

def _simulate_doc_ack(
    styled_response: str,
    context_mode: str,
    has_explicit_doc_token: bool,
    doc_just_activated: bool,
) -> str:
    """Симуляція v3.7 state-aware doc ack prefix."""
    if context_mode == "doc" and doc_just_activated:
        ack = "По звіту дивлюсь." if has_explicit_doc_token else "Працюємо зі звітом."
        if not styled_response.startswith(ack):
            return f"{ack}\n{styled_response}"
    return styled_response


class TestStateAwareDocAck:
    def test_explicit_token_ack(self):
        out = _simulate_doc_ack(
            "Прибуток склав 500 тис грн.", "doc", True, True
        )
        assert out.startswith("По звіту дивлюсь.")

    def test_general_doc_ack(self):
        out = _simulate_doc_ack(
            "Прибуток склав 500 тис грн.", "doc", False, True
        )
        assert out.startswith("Працюємо зі звітом.")

    def test_no_ack_if_focus_was_already_active(self):
        # doc_just_activated=False — фокус вже був активний
        out = _simulate_doc_ack(
            "Прибуток склав 500 тис грн.", "doc", True, False
        )
        assert not out.startswith("По звіту дивлюсь.")
        assert out == "Прибуток склав 500 тис грн."

    def test_no_ack_in_general_mode(self):
        out = _simulate_doc_ack(
            "Гаразд, зрозуміло.", "general", True, True
        )
        assert out == "Гаразд, зрозуміло."

    def test_no_duplicate_ack(self):
        ack = "Працюємо зі звітом."
        out = _simulate_doc_ack(ack + "\nДані доступні.", "doc", False, True)
        assert out.count(ack) == 1


# ─── 6. UX awareness phrase suppression симуляція ────────────────────────────

def _apply_awareness_guard(response: str, text: str) -> tuple[str, bool]:
    """Симуляція v3.7 awareness phrase guard."""
    if _DOC_AWARENESS_RE.search(response):
        replaced = re.sub(
            _DOC_AWARENESS_RE,
            lambda m: build_mode_clarifier(text),
            response,
            count=1,
        )
        return replaced, True
    return response, False


class TestAwarenessGuard:
    def test_pamyatayu_replaced(self):
        resp = "Так, пам'ятаю документ який ви надіслали."
        out, replaced = _apply_awareness_guard(resp, "що там?")
        assert replaced
        assert "пам" not in out.lower() or "пам'ятаю" not in out.lower()

    def test_ne_bachu_replaced(self):
        resp = "Не бачу його перед собою, надішліть ще раз."
        out, replaced = _apply_awareness_guard(resp, "листя плями")
        assert replaced
        assert "фото" in out.lower() or "рослин" in out.lower()

    def test_clean_response_unchanged(self):
        resp = "Прибуток у першому кварталі склав 1.2М грн."
        out, replaced = _apply_awareness_guard(resp, "прибуток?")
        assert not replaced
        assert out == resp

    def test_only_first_occurrence_replaced(self):
        """count=1 — замінюємо тільки перше входження."""
        resp = "Так, пам'ятаю. Не бачу його."
        out, replaced = _apply_awareness_guard(resp, "що там?")
        assert replaced
        # Тільки перше замінено — друге може лишитись (count=1)
        assert out.count("?") >= 1


# ─── 7. Vision intro polish ───────────────────────────────────────────────────

class TestVisionIntroPhrases:
    GOOD_STARTS = [
        "Схоже на хлороз листя.",
        "Ймовірно дефіцит мікроелементів.",
        "Виглядає як рання стадія хвороби.",
    ]
    BAD_STARTS = [
        "На фото видно жовтіння листя.",
    ]

    def test_good_vision_intros_not_blocked(self):
        for phrase in self.GOOD_STARTS:
            assert not _VISION_INTRO_RE.search(phrase), f"False positive: '{phrase}'"

    def test_bad_vision_intro_blocked(self):
        for phrase in self.BAD_STARTS:
            assert _VISION_INTRO_RE.search(phrase), f"Not blocked: '{phrase}'"
