"""M15-Alert: die ENTPRELLUNG ist der ganze Bau — also wird sie geprueft.

ANLASS (2026-08-14, User: „wenn es eine Empfehlung gibt (gruen), schicke eine
Telegram-Nachricht").

⚠⚠ Roh gemessen springt die Karte **85x am Tag** auf gruen (`m15_states`, 3 Tage
nach der Hysterese vom 12.08.). Ohne Entprellung waere die Meldung Spam und
damit wertlos — genau die Sorte Funktion, die man nach zwei Tagen stummschaltet.
Die Stufen und ihre gemessene Wirkung:
    Flanke allein                      85,0/Tag
    + Level+Richtung als Schluessel    23,7/Tag
    + 30 min Mindestabstand            13,3/Tag
    + Fenster 8-20 Uhr                  7,7/Tag
Deshalb liegt der Schwerpunkt der Tests auf dem, was NICHT gesendet werden darf.
"""
from __future__ import annotations

import time
from types import SimpleNamespace

import pytest


class _Fake:
    """Minimal-Engine mit genau den Feldern, die `_check_m15_alert` anfasst."""

    def __init__(self, **kw):
        from core.engine import TradingEngine
        self._check_m15_alert = TradingEngine._check_m15_alert.__get__(self)
        self._m15_alert = kw.get("an", True)
        self._m15_alert_cd = kw.get("cd", 1800.0)
        self._m15_alert_hours = kw.get("hours", (0, 24))
        self._m15_alert_war = False
        self._m15_alert_ts = 0.0
        self._m15_alert_key = None
        self.cfg = {"telegram": {"enabled": "true", "bot_token": "t", "chat_id": "c"}}


def _karte(status="gruen", lesart="auf", level=82.069, pb=69):
    return {"status": status, "lesart": lesart,
            "nah": {"seite": "res", "level": level, "gap_atr15": 0.48, "p_break": pb},
            "bias": {"verboten": "SHORT"}, "kosten": {"ratio": 0.11}}


@pytest.fixture
def gesendet(monkeypatch):
    """Faengt jeden Telegram-Versand ab — kein Netz im Test (conftest-Regel)."""
    raus = []
    monkeypatch.setattr("core.engine.send_telegram",
                        lambda text, *a, **k: raus.append(text))
    return raus


def test_gruene_flanke_sendet(gesendet):
    e = _Fake()
    e._check_m15_alert(_karte())
    assert len(gesendet) == 1
    assert "Setup bereit" in gesendet[0]


def test_zustand_sendet_nur_EINMAL(gesendet):
    """⚠ Die wichtigste Zeile: gruen bleibt gruen ueber viele Ticks. Ohne
    Flankenerkennung kaeme im Sekundentakt eine Nachricht."""
    e = _Fake()
    for _ in range(50):
        e._check_m15_alert(_karte())
    assert len(gesendet) == 1


def test_flankenerkennung_ISOLIERT(gesendet):
    """⚠⚠ DIESER Test existiert wegen einer Mutationsprobe (2026-08-14): das
    Entfernen der Flankenerkennung liess ALLE anderen Tests gruen — weil der
    Cooldown und der 4-h-Schluessel dasselbe mit abfangen. Zwei Schutzschichten,
    die sich gegenseitig maskieren, sind genau das Muster, das im Projekt beim
    Fill-Dedup schon dokumentiert ist.

    Isoliert wird sie so: Cooldown AUS und ein Level, das bei jedem Tick leicht
    wandert (real der Normalfall — der Kurs laeuft, der naechste Pivot rutscht).
    Damit greift WEDER Cooldown NOCH Schluessel; bleibt nur die Flanke.
    """
    e = _Fake(cd=0.0)
    for i in range(30):
        e._check_m15_alert(_karte(level=82.0 + i * 0.01))
    assert len(gesendet) == 1, (
        "ohne Flankenerkennung kaeme hier eine Nachricht je Tick")


def test_flackern_am_selben_level_sendet_nicht_erneut(gesendet):
    """gruen -> gelb -> gruen am SELBEN Level ist dasselbe Setup, das flackert.
    Gemessen der Haupttreiber: 85 -> 23,7/Tag allein durch diesen Schluessel."""
    e = _Fake()
    for _ in range(10):
        e._check_m15_alert(_karte())
        e._check_m15_alert(_karte(status="gelb"))
    assert len(gesendet) == 1


def test_neues_level_sendet_trotz_cooldown_nicht(gesendet):
    """⚠ Der Mindestabstand hat VORRANG vor dem neuen Level — sonst genuegt ein
    um einen Cent verschobenes Level, um die Entprellung auszuhebeln."""
    e = _Fake()
    e._check_m15_alert(_karte(level=82.069))
    e._check_m15_alert(_karte(status="gelb"))
    e._check_m15_alert(_karte(level=83.500))       # anderes Level, aber sofort
    assert len(gesendet) == 1


def test_neues_level_sendet_nach_cooldown(gesendet):
    e = _Fake(cd=0.0)
    e._check_m15_alert(_karte(level=82.069))
    e._check_m15_alert(_karte(status="gelb"))
    e._check_m15_alert(_karte(level=83.500))
    assert len(gesendet) == 2


def test_gleiches_level_wiederholt_nicht_vor_vier_stunden(gesendet):
    """Ohne Cooldown, aber gleicher Schluessel -> erst nach _ALERT_NEU_S wieder."""
    e = _Fake(cd=0.0)
    e._check_m15_alert(_karte(level=82.069))
    e._check_m15_alert(_karte(status="gelb"))
    e._check_m15_alert(_karte(level=82.069))
    assert len(gesendet) == 1


def test_ausserhalb_des_stundenfensters_still(gesendet):
    e = _Fake(hours=(23, 24))                      # jetzt ist praktisch nie drin
    if time.localtime().tm_hour == 23:
        pytest.skip("Testzeit liegt zufaellig im Fenster")
    e._check_m15_alert(_karte())
    assert gesendet == []


def test_unklare_lesart_sendet_nicht(gesendet):
    """45-55 % ist ein Muenzwurf — daraus eine Meldung zu machen waere genau der
    Fehler, den die Karte an drei Stellen schon gemacht hat."""
    e = _Fake()
    e._check_m15_alert(_karte(lesart="unklar"))
    assert gesendet == []


def test_gelb_und_rot_senden_nicht(gesendet):
    e = _Fake()
    e._check_m15_alert(_karte(status="gelb"))
    e._check_m15_alert(_karte(status="rot"))
    assert gesendet == []


def test_abgeschaltet_sendet_nicht(gesendet):
    e = _Fake(an=False)
    e._check_m15_alert(_karte())
    assert gesendet == []


def test_telegram_deaktiviert_sendet_nicht(gesendet):
    e = _Fake()
    e.cfg["telegram"]["enabled"] = "false"
    e._check_m15_alert(_karte())
    assert gesendet == []


def test_text_nennt_die_gemessene_lesart_und_warnt(gesendet):
    """Die Zahl muss WORTGLEICH zur Karte und zum Chart sein (v1.35-Fix), und der
    Text darf keine Prognose behaupten."""
    e = _Fake()
    e._check_m15_alert(_karte(pb=33))
    t = gesendet[0]
    assert "67 % Abprall" in t, "P(Abprall) wie in Karte und Chart"
    assert "keine Richtungsprognose" in t
    assert "erlaubt wäre LONG" in t          # Veto SHORT -> erlaubt LONG


def test_kein_absturz_ohne_daten(gesendet):
    e = _Fake()
    e._check_m15_alert(None)
    e._check_m15_alert({})
    e._check_m15_alert({"status": "gruen", "lesart": "auf"})   # `nah` fehlt
    assert isinstance(gesendet, list)


if __name__ == "__main__":
    raise SystemExit(pytest.main([__file__, "-v"]))
