from __future__ import annotations

import argparse
import json
from datetime import datetime
from pathlib import Path
from typing import Dict, List

from trend_features import (
    TrendFeatures,
    build_trend_features_from_polygon,
    score_from_features,
)

DEBUG_SYMBOLS = {"AAPL", "AMD"}

def load_tickers(path: Path) -> List[str]:
    """
    Lädt die 20 Ticker aus data/tickers.json.
    Erwartet eine Liste von Strings oder Objekten mit "symbol".
    """
    if not path.exists():
        raise FileNotFoundError(f"Tickers-Datei nicht gefunden: {path}")
    with path.open(encoding="utf-8") as f:
        data = json.load(f)

    tickers: List[str] = []
    if isinstance(data, list):
        for item in data:
            if isinstance(item, dict) and "symbol" in item:
                tickers.append(str(item["symbol"]).upper())
            else:
                tickers.append(str(item).upper())
    else:
        raise ValueError("tickers.json muss eine Liste sein")

    return tickers


def load_scan_config(path: Path) -> Dict:
    """
    Lädt die Gewichtungen und Filter aus data/scan_config.json.
    """
    if not path.exists():
        raise FileNotFoundError(f"Scan-Config nicht gefunden: {path}")
    with path.open(encoding="utf-8") as f:
        return json.load(f)


def load_market_bias(path: Path) -> str:
    """
    Versucht, den Gesamtmarkt-Bias (UP/DOWN/NEUTRAL) aus market.json zu lesen.
    Ist tolerant bei den Feldnamen und fällt bei Problemen auf NEUTRAL zurück.
    """
    if not path.exists():
        return "NEUTRAL"

    try:
        with path.open(encoding="utf-8") as f:
            data = json.load(f)
    except Exception:
        return "NEUTRAL"

    if not isinstance(data, dict):
        return "NEUTRAL"

    raw = (
        data.get("bias")
        or data.get("trend")
        or data.get("market_bias")
        or data.get("market_trend")
    )

    if not isinstance(raw, str):
        return "NEUTRAL"

    txt = raw.upper()
    if any(k in txt for k in ("UP", "LONG", "BULL")):
        return "UP"
    if any(k in txt for k in ("DOWN", "SHORT", "BEAR")):
        return "DOWN"
    return "NEUTRAL"


def classify_trend(features: TrendFeatures) -> str:
    """
    Vereinfachte Trend-Logik für den Fullscan.
    Rückgabe: 'LONG', 'SHORT' oder 'SIDEWAYS'.
    Nutzt D1/H1/M15-Trend aus den Trendfeatures.
    """
    d1 = (features.trend_D1 or "").upper()
    h1 = (features.trend_H1 or "").upper()
    m15 = (features.trend_M15 or "").upper()

    # Starkes Long-Setup: D1 LONG + (H1 oder M15 UP)
    if d1 == "LONG" and (h1 == "UP" or m15 == "UP"):
        return "LONG"

    # Starkes Short-Setup: D1 SHORT + (H1 oder M15 DOWN)
    if d1 == "SHORT" and (h1 == "DOWN" or m15 == "DOWN"):
        return "SHORT"

    # Alles andere: Seitwärts / Neutral
    return "SIDEWAYS"


def build_reasons(features: TrendFeatures, score: int) -> List[str]:
    """
    Erzeugt eine kurze Liste von Gründen für die Einstufung.
    Maximal 4 Einträge für sauberes UI.
    """
    reasons: List[str] = []

    if features.bias_d1 == "LONG":
        reasons.append("D1 über EMA200 (Bias LONG)")
    elif features.bias_d1 == "SHORT":
        reasons.append("D1 unter EMA200 (Bias SHORT)")

    if features.h1_trend == "UP":
        reasons.append("H1 Trend UP (EMA20 > EMA50)")
    elif features.h1_trend == "DOWN":
        reasons.append("H1 Trend DOWN (EMA20 < EMA50)")

    if features.intraday_trend_m15 == "UP":
        reasons.append("M15 Intraday-Aufwärtsstruktur")
    elif features.intraday_trend_m15 == "DOWN":
        reasons.append("M15 Intraday-Abwärtsstruktur")

    if features.rvol is not None:
        reasons.append(f"RVOL {features.rvol:.1f}")

    if features.atrp is not None:
        reasons.append(f"ATR% {features.atrp:.1f}")

    if features.counter_trend:
        reasons.append("Counter-Trend-Setup")

    # Score als kurzer Überblick
    reasons.append(f"Score {score}")

    return reasons[:4]


def run_fullscan(preopen: bool = False) -> Dict:
    """
    Führt den Fullmarket-Scan für die 20 Ticker aus und schreibt:
    - data/watchlists.json
    - data/fullscan_last_run.json

    Rückgabe: Status-Dict (wird am Ende als JSON gedruckt).
    """
    base_dir = Path(__file__).resolve().parent.parent  # /kleiner
    data_dir = base_dir / "data"
    market_dir = data_dir / "market"

    tickers_path = data_dir / "tickers.json"
    scan_config_path = data_dir / "scan_config.json"
    market_path = market_dir / "market.json"

    tickers = load_tickers(tickers_path)
    scan_config = load_scan_config(scan_config_path)
    market_bias = load_market_bias(market_path)

    long_list: List[Dict] = []
    short_list: List[Dict] = []
    sideways_list: List[Dict] = []

    for symbol in tickers:
        # 1. Trendfeatures v2 berechnen (Polygon + Fallback)
        features = build_trend_features_from_polygon(
            symbol=symbol,
            scan_config=scan_config,
            market_bias=market_bias,
        )

        # DEBUG-Ausgabe für ausgewählte Symbole
        if symbol in DEBUG_SYMBOLS:
            print("\n[DEBUG]", symbol, "Features:")
            print("  trend_simple:", features.trend)
            print("  d1_trend    :", features.d1_trend, "| trend_D1:", features.trend_D1)
            print("  h1_trend    :", features.h1_trend, "| trend_H1:", features.trend_H1)
            print("  m15_trend   :", features.m15_trend, "| trend_M15:", features.trend_M15)
            print("  ATR%        :", features.atr_percent)
            print("  RVOL        :", features.rvol)
            
        # 2. Score 0–100 errechnen
        score = score_from_features(
            features=features,
            scan_config=scan_config,
            market_bias=market_bias,
        )

        # 3. Kategorie (Long/Short/Seitwärts) bestimmen
        trend_label = classify_trend(features)

                # 4. Gründe (Reasons) bauen
        reasons = build_reasons(features, score)

        # 5. Kurs & Session-% aus den Features holen
        try:
            last_price = float(features.get("last_price") or 0.0)
        except Exception:
            last_price = 0.0
        try:
            pct_session = float(features.get("pct_session") or 0.0)
        except Exception:
            pct_session = 0.0

        # 6. Item für Watchlist aufbauen
        item = {
            "symbol": features.symbol,
            "score": score,
            "trend": trend_label,
            "setup_type": features.setup_type,
            "bias_d1": features.bias_d1,
            "h1_trend": features.h1_trend,
            "intraday_trend_m15": features.intraday_trend_m15,
            "counter_trend": features.counter_trend,
            "vwap_relation": features.vwap_relation,
            "reasons": reasons,
            # Kurs + Session-Veränderung (wird im Frontend als Kurs / +/- % angezeigt)
            "price": last_price,
            "change": pct_session,
        }

        # Sicherstellen, dass Trend / ATR% / RVOL fürs Frontend gesetzt sind
        item["trend"] = trend_label
        try:
            atr_val = float(features.atr_percent or features.atrp or 0)
        except Exception:
            atr_val = 0.0

        # Alias, falls das Frontend noch das alte Feld 'atrp' benutzt
        item["atr_percent"] = atr_val
        item["atrp"] = atr_val

        try:
            rvol_val = float(features.rvol or 0)
        except Exception:
            rvol_val = 0.0

        item["rvol"] = rvol_val

        # zusätzliches indicators-Objekt fürs Frontend (Tickerliste liest item.indicators.*)
        item["indicators"] = {
            "rvol": rvol_val,
            "atrp": atr_val,
            "atr_percent": atr_val,
        }


        if trend_label == "LONG":
            long_list.append(item)
        elif trend_label == "SHORT":
            short_list.append(item)
        else:
            sideways_list.append(item)

    # Score-sortierung (höchster Score zuerst)
    long_list.sort(key=lambda x: x["score"], reverse=True)
    short_list.sort(key=lambda x: x["score"], reverse=True)
    sideways_list.sort(key=lambda x: x["score"], reverse=True)

    now_utc = datetime.utcnow().replace(microsecond=0).isoformat() + "Z"

    watchlists = {
        "updated_utc": now_utc,
        "market_bias": market_bias,
        "long": long_list,
        "short": short_list,
        "sideways": sideways_list,
    }

    # Dateien schreiben
    watchlists_path = data_dir / "watchlists.json"
    fullscan_status_path = data_dir / "fullscan_last_run.json"

    with watchlists_path.open("w", encoding="utf-8") as f:
        json.dump(watchlists, f, ensure_ascii=False, indent=2)

        status = {
        "last_run_utc": now_utc,
        "status": "success",
        "tickers_total": len(tickers),
        "long": len(long_list),
        "short": len(short_list),
        "sideways": len(sideways_list),
        "message": (
            "Fullscan (Pre-Open) erfolgreich mit Trendfeatures v2"
            if preopen
            else "Fullscan (Intraday) erfolgreich mit Trendfeatures v2"
        ),
    }

    # 1) Letzten Lauf wie bisher für das Dashboard speichern
    with fullscan_status_path.open("w", encoding="utf-8") as f:
        json.dump(status, f, ensure_ascii=False, indent=2)

    # 2) NEU: History der letzten Fullmarket-Scans pflegen
    history_path = data_dir / "fullscan_history.json"
    history = []

    if history_path.exists():
        try:
            with history_path.open("r", encoding="utf-8") as f:
                history = json.load(f)
            if not isinstance(history, list):
                history = []
        except Exception:
            history = []

    # neuen Lauf vorne einfügen
    history.insert(0, status)
    # z.B. nur die letzten 50 Läufe behalten
    history = history[:50]

    with history_path.open("w", encoding="utf-8") as f:
        json.dump(history, f, ensure_ascii=False, indent=2)

    return status


def main() -> None:
    parser = argparse.ArgumentParser(description="Fullmarket-Scan für 20 Ticker")
    parser.add_argument(
        "--preopen",
        action="store_true",
        help="Pre-Open-Lauf (wird vor Börseneröffnung aufgerufen)",
    )
    args = parser.parse_args()

    try:
        status = run_fullscan(preopen=args.preopen)
    except Exception as exc:
        # Fehler in Status schreiben, damit Dashboard etwas anzeigen kann
        now_utc = datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
        status = {
            "last_run_utc": now_utc,
            "status": "error",
            "tickers_total": 0,
            "long": 0,
            "short": 0,
            "sideways": 0,
            "message": f"Fullscan v2 Fehler: {exc}",
        }

    print("fullscan OK:")
    print(json.dumps(status, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()
