"""
core/daily_levels.py — Tägliche WTI-Intraday-Level per Web-Recherche
=====================================================================
Holt einmal morgens (Engine-Scheduler) über **z.ai/GLM mit Web-Suche**
(`web_search`-Tool, Thinking aus) die heutigen Schlüssel-Level (Support /
Widerstandsband / Bias) für WTI und liefert sie als **Kontext**: fließen in
S/R-Anzeige, Close-Alarme und Verdict — NICHT in die Handelsrichtung
(Dritt-Prognose, kein gemessener Edge).

Strenge Plausibilitätsprüfung gegen den aktuellen Kurs verhindert, dass
halluzinierte Zahlen als Zonen landen (fail-safe: bei Zweifel kein Update).
"""
from __future__ import annotations
import threading
import time

from core.logger import get_logger

log = get_logger("daily")

_MAX_DEV = 0.12   # Level muss innerhalb ±12 % des Kurses liegen (sonst verworfen)

_PROMPT = (
    "Du bist Rohstoff-Marktanalyst. Suche im Web die heutige INTRADAY-Lage für "
    "WTI-Rohöl (US Crude, aktueller Kurs ~{price:.2f} USD). Nenne die für HEUTE "
    "wichtigsten technischen Level rund um den aktuellen Kurs.\n\n"
    "Antworte AUSSCHLIESSLICH in genau diesem Format (nur Zahlen, Punkt als "
    "Dezimaltrenner, deutsche Sprache):\n"
    "SUPPORT: <preis unter dem Kurs>\n"
    "RESISTANCE_LOW: <preis über dem Kurs>\n"
    "RESISTANCE_HIGH: <preis über dem Kurs, >= RESISTANCE_LOW>\n"
    "BIAS: bullish|bearish|neutral\n"
    "SUMMARY: <1-2 Sätze, nur preisbewegende Faktoren>\n\n"
    "Die Level müssen NAHE am aktuellen Kurs liegen (Intraday, keine Jahresziele). "
    "Keine Disclaimer, keine Einleitung."
)


class DailyLevels:
    def __init__(self, api_key: str, base_url: str = "https://api.z.ai/api/paas/v4",
                 model: str = "glm-4.5-flash"):
        self.api_key = (api_key or "").strip()
        self.base_url = (base_url or "https://api.z.ai/api/paas/v4").rstrip("/")
        self.model   = (model or "glm-4.5-flash").strip()
        self.support: float | None = None
        self.res_lo:  float | None = None
        self.res_hi:  float | None = None
        self.bias:    str = "neutral"
        self.summary: str = ""
        self.ts:      float = 0.0
        self.error:   str | None = None
        self._lock = threading.Lock()

    def is_configured(self) -> bool:
        return bool(self.api_key)

    @staticmethod
    def _num(line: str):
        s = line.split(":", 1)[1] if ":" in line else line
        s = s.replace(",", ".")
        buf = ""
        for c in s:
            if c.isdigit() or c == ".":
                buf += c
            elif buf:
                break
        try:
            return float(buf)
        except ValueError:
            return None

    def _call_llm(self, prompt: str) -> str:
        """z.ai/GLM (OpenAI-kompatibel) mit Web-Suche + Thinking AUS (sonst geht
        das Token-Budget komplett ins „Denken")."""
        import requests
        resp = requests.post(
            self.base_url + "/chat/completions",
            headers={"Authorization": "Bearer " + self.api_key},
            json={"model": self.model,
                  "messages": [{"role": "user", "content": prompt}],
                  "tools": [{"type": "web_search",
                             "web_search": {"enable": True, "search_result": True}}],
                  "thinking": {"type": "disabled"},
                  "max_tokens": 500},
            timeout=90)
        resp.raise_for_status()
        return (resp.json()["choices"][0]["message"].get("content") or "").strip()

    def run(self, price: float) -> bool:
        """Holt + validiert die Tages-Level. price = aktueller Kurs (Anker +
        Plausibilitätsbasis). True bei erfolgreichem, plausiblem Update."""
        if not self.is_configured():
            with self._lock:
                self.error = "Kein z.ai-Key"
            return False
        if not price or price <= 0:
            with self._lock:
                self.error = "Kein Kurs als Anker"
            return False
        try:
            text = self._call_llm(_PROMPT.format(price=price))

            sup = rlo = rhi = None
            bias, summary = "neutral", ""
            for raw in text.split("\n"):
                low = raw.strip().lower()
                if low.startswith("support:"):          sup = self._num(raw)
                elif low.startswith("resistance_low:"):  rlo = self._num(raw)
                elif low.startswith("resistance_high:"): rhi = self._num(raw)
                elif low.startswith("bias:"):
                    v = low.split(":", 1)[1]
                    bias = ("bullish" if "bull" in v else
                            "bearish" if "bear" in v else "neutral")
                elif low.startswith("summary:"):
                    summary = raw.split(":", 1)[1].strip()

            ok, why = self._validate(price, sup, rlo, rhi)
            if not ok:
                with self._lock:
                    self.error = f"unplausibel: {why}"
                log.warning(f"Tages-Level verworfen ({why}) — "
                            f"sup={sup} rlo={rlo} rhi={rhi} bei Kurs {price:.2f}")
                return False

            with self._lock:
                self.support, self.res_lo, self.res_hi = sup, rlo, rhi
                self.bias, self.summary = bias, summary
                self.ts, self.error = time.time(), None
            log.info(f"Tages-Level: Sup {sup:.2f} · Res {rlo:.2f}-{rhi:.2f} · "
                     f"Bias {bias} · {summary[:70]}")
            return True
        except Exception as e:
            with self._lock:
                self.error = str(e)[:120]
            log.warning(f"DailyLevels.run: {e}")
            return False

    def _validate(self, price, sup, rlo, rhi):
        if sup is None or rlo is None or rhi is None:
            return False, "Level fehlen"
        if not (sup < price < rhi):
            return False, "Reihenfolge support<Kurs<resistance verletzt"
        if rlo > rhi:
            return False, "res_low>res_high"
        for lv in (sup, rlo, rhi):
            if abs(lv - price) / price > _MAX_DEV:
                return False, f"{lv} >{_MAX_DEV*100:.0f}% vom Kurs entfernt"
        return True, ""

    def zone_lines(self) -> list[float]:
        with self._lock:
            return [x for x in (self.support, self.res_lo, self.res_hi)
                    if x is not None]

    def snapshot(self) -> dict:
        with self._lock:
            return {"support": self.support, "res_lo": self.res_lo,
                    "res_hi": self.res_hi, "bias": self.bias,
                    "summary": self.summary, "ts": self.ts, "error": self.error}
