"""
CEK-DATA.PY — Modul Neurobro (Siap Copy-Paste ke Docs)
Struktur fungsi agar max 1500 kata per function.

1. scheduler_check() — Cek jadwal trading
2. hitung_indikator() — Hitung semua indikator teknikal
3. check_volume_spike() — Cek volume multiplier (Layer 1a)
4. check_value_traded() — Cek nilai transaksi (Layer 1b)
5. check_price_ceiling() — Cek batas harga scalp murah (Layer 1c)
6. filter_likuiditas() — Layer 1 utama (gabung 3a,3b,3c)
7. score_trend() — Score SMA50 position (Layer 2a)
8. score_entry_timing() — Score EMA9/21 cross (Layer 2b)
9. score_volume_spike() — Score volume spike (Layer 2c)
10. score_rsi() — Score RSI momentum (Layer 2d)
11. score_price_action() — Score 3-bar pattern (Layer 2e)
12. get_action_label() — Determine action based on score
13. scoring_technikal() — Layer 2 main (gabung 7a-7e)
"""

import pandas as pd
import numpy as np

# ============================================================
# 1. SCHEDULER CHECK
# ============================================================

def scheduler_check(
    run_days=None,
    run_hour_start=9,
    run_hour_end=16,
    enforce_schedule=False,
    timezone="Asia/Jakarta"
):
    """Cek apakah sekarang dalam jadwal trading."""
    from datetime import datetime
    from zoneinfo import ZoneInfo

    if run_days is None:
        run_days = [0, 1, 2, 3, 4]
    if not enforce_schedule:
        return {"allowed": True, "now": datetime.now(ZoneInfo(timezone)).strftime("%Y-%m-%d %H:%M:%S"), "reason": "schedule_disabled"}
    now = datetime.now(ZoneInfo(timezone))
    if now.weekday() not in run_days:
        return {"allowed": False, "now": now.strftime("%Y-%m-%d %H:%M:%S"), "reason": f"Hari {now.strftime('%A')} di luar run_days"}
    if not (run_hour_start <= now.hour < run_hour_end):
        return {"allowed": False, "now": now.strftime("%Y-%m-%d %H:%M:%S"), "reason": f"Jam {now.hour}:00 di luar jadwal"}
    return {"allowed": True, "now": now.strftime("%Y-%m-%d %H:%M:%S"), "reason": "within_schedule"}


# ============================================================
# 2. HITUNG SEMUA INDIKATOR
# ============================================================

def hitung_indikator(df, ema_fast=9, ema_slow=21, rsi_period=14, sma_trend_period=50, volume_avg_period=20):
    """Hitung semua indikator teknikal (RSI, EMA, SMA50, Volume, Candle)."""
    out = df.copy()
    delta = out["Close"].diff()
    gain = delta.clip(lower=0)
    loss = -delta.clip(upper=0)
    avg_gain = gain.ewm(alpha=1 / rsi_period, min_periods=rsi_period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1 / rsi_period, min_periods=rsi_period, adjust=False).mean()
    rs = avg_gain / avg_loss
    out["rsi"] = 100 - (100 / (1 + rs))
    out["ema_fast"] = out["Close"].ewm(span=ema_fast, adjust=False).mean()
    out["ema_slow"] = out["Close"].ewm(span=ema_slow, adjust=False).mean()
    out["sma_trend"] = out["Close"].rolling(sma_trend_period).mean()
    out["vol_avg20"] = out["Volume"].rolling(volume_avg_period).mean()
    out["vol_ratio"] = out["Volume"] / out["vol_avg20"]
    out["is_green"] = out["Close"] > out["Open"]
    out["body"] = (out["Close"] - out["Open"]).abs()
    out["range"] = (out["High"] - out["Low"]).replace(0, np.nan)
    out["body_ratio"] = out["body"] / out["range"]
    return out


# ============================================================
# 3. LAYER 1A: CHECK VOLUME SPIKE
# ============================================================

def check_volume_spike(daily_df, mode="long", scalp_min_volume_mult=5, long_min_volume_mult=3, volume_avg_period=20):
    if daily_df is None or len(daily_df) < volume_avg_period + 1:
        return {"status": "unknown", "reason": "data harian belum cukup"}
    avg_vol20 = daily_df["Volume"].iloc[-(volume_avg_period + 1):-1].mean()
    today_vol = daily_df["Volume"].iloc[-1]
    if not avg_vol20 or pd.isna(avg_vol20):
        return {"status": "unknown", "reason": "rata-rata volume 0/NaN"}
    threshold = scalp_min_volume_mult if mode == "scalp" else long_min_volume_mult
    ok = today_vol / avg_vol20 >= threshold
    return {"status": "pass" if ok else "fail","ratio": round(float(today_vol / avg_vol20), 2),"threshold": threshold}
def check_value_traded(daily_df, min_avg_value_traded=1_000_000_000, volume_avg_period=20):
    if daily_df is None or len(daily_df) < volume_avg_period:
        return {"status": "unknown", "reason": "data kurang dari 20 hari"}
    avg_value = (daily_df["Close"] * daily_df["Volume"]).iloc[-volume_avg_period:].mean()
    ok = avg_value >= min_avg_value_traded
    return {"status": "pass" if ok else "fail","value": round(float(avg_value)),"min_required": min_avg_value_traded}

def check_price_ceiling(daily_df, price_ceiling, mode="scalp"):
    if mode != "scalp":
        return {"status": "skip", "reason": "hanya untuk mode scalp"}
    if price_ceiling is None:
        return {"status": "skip", "reason": "price_ceiling None = disable"}
    if daily_df is None or len(daily_df) == 0:
        return {"status": "unknown", "reason": "data harian kosong"}
    close = float(daily_df["Close"].iloc[-1])
    ok = close < price_ceiling
    return {"status": "pass" if ok else "fail","close": round(close, 2),"ceiling": price_ceiling}

def filter_likuiditas(
    daily_df,
    mode="long",
    min_avg_value_traded=1_000_000_000,
    scalp_min_volume_mult=5,
    long_min_volume_mult=3,
    price_ceiling=None
):
    checks = {}
    warnings = []
    fails = []
    vol_result = check_volume_spike(daily_df, mode, scalp_min_volume_mult, long_min_volume_mult)
    checks["volume_spike"] = vol_result
    if vol_result["status"] == "fail":
        fails.append(f"volume {vol_result['ratio']}x < {vol_result['threshold']}x")
    elif vol_result["status"] == "unknown":
        warnings.append(vol_result.get("reason", "volume check unknown"))
    val_result = check_value_traded(daily_df, min_avg_value_traded)
    checks["value_traded"] = val_result
    if val_result["status"] == "fail":
        fails.append(f"value traded Rp{val_result['value']:,.0f} < Rp{val_result['min_required']:,.0f}")
    elif val_result["status"] == "unknown":
        warnings.append(val_result.get("reason", "value check unknown"))

    if mode == "scalp" and price_ceiling is not None:
        price_result = check_price_ceiling(daily_df, price_ceiling, mode)
        checks["price_ceiling"] = price_result
        if price_result["status"] == "fail":
            fails.append(f"harga Rp{price_result['close']:,.0f} > Rp{price_result['ceiling']:,.0f}")
    passed = len(fails) == 0
    return {"passed": passed, "checks": checks, "fails": fails, "warnings": warnings}


# ============================================================
# 7. SCORE TREND (LAYER 2A)
# ============================================================

def score_trend(daily_df, trend_strong_pct=3, trend_moderate_pct=1, trend_at_sma_band_pct=1, trend_max_points=25):
    if daily_df is None or "sma_trend" not in daily_df.columns or pd.isna(daily_df["sma_trend"].iloc[-1]):
        return {"points": 0, "max_points": trend_max_points, "reason": "SMA50 data not enough"}
    sma50 = float(daily_df["sma_trend"].iloc[-1])
    close = float(daily_df["Close"].iloc[-1])
    diff_pct = (close - sma50) / sma50 * 100
    if diff_pct > trend_strong_pct:
        points = 25
    elif diff_pct > trend_moderate_pct:
        points = 20
    elif diff_pct >= -trend_at_sma_band_pct:
        points = 10
    else:
        points = 0
    return {"points": points,"max_points": trend_max_points,"sma50": round(sma50, 2),"close": round(close, 2),"diff_pct": round(diff_pct, 2)}
def score_entry_timing(pdf, ema_slope_lookback=3, ema_flat_threshold_pct=0.15, entry_max_points=20):
    if len(pdf) < ema_slope_lookback + 1:
        return {"points": 0, "max_points": entry_max_points, "reason": "EMA data not enough"}
    ema9 = pdf["ema_fast"].iloc[-1]; ema21 = pdf["ema_slow"].iloc[-1]; close = pdf["Close"].iloc[-1]
    if pd.isna(ema9) or pd.isna(ema21):
        return {"points": 0, "max_points": entry_max_points, "reason": "EMA data not enough"}
    ema9_prev = pdf["ema_fast"].iloc[-1 - ema_slope_lookback]
    ema21_prev = pdf["ema_slow"].iloc[-1 - ema_slope_lookback]
    ema9_slope = (ema9 - ema9_prev) / ema9_prev * 100 if ema9_prev else 0
    ema21_slope = (ema21 - ema21_prev) / ema21_prev * 100 if ema21_prev else 0
    if ema9 <= ema21:
        points = 0  # bearish
    elif close > ema21 and (ema9_slope > ema_flat_threshold_pct and ema21_slope > ema_flat_threshold_pct):
        points = 20  # full cross rising
    elif ema21 <= close <= ema9:
        points = 10  # consolidation
    else:
        points = 5  # flat or weak
    return {"points": points,"max_points": entry_max_points,"ema9": round(float(ema9), 2),"ema21": round(float(ema21), 2)}


# ============================================================
# 9. SCORE VOLUME SPIKE (LAYER 2C)
# ============================================================

def score_volume_spike(pdf, volume_lookback_bars=3, volume_tiers=None, volume_max_points=20):
    if volume_tiers is None:
        volume_tiers = [(80, 20), (60, 17), (40, 14), (20, 8)]
    recent = pdf.iloc[-volume_lookback_bars:]
    if recent["vol_ratio"].isna().all():
        return {"points": 0, "max_points": volume_max_points, "reason": "volume data not enough"}
    if pd.isna(recent.loc[recent["vol_ratio"].idxmax(), "vol_ratio"]):
        return {"points": 0, "max_points": volume_max_points, "reason": "volume data not enough"}
    is_green = bool(recent.loc[recent["vol_ratio"].idxmax(), "is_green"])
    spike_pct = float((recent.loc[recent["vol_ratio"].idxmax(), "vol_ratio"] - 1) * 100)
    if not is_green:
        points = 0  # candle merah = distribusi
    else:
        for min_pct, pts in volume_tiers:
            if spike_pct >= min_pct:
                points = pts
                break
        else:
            points = 0
    return {"points": points,"max_points": volume_max_points,"spike_pct": round(spike_pct, 1)}
def score_rsi(pdf, rsi_tiers=None, rsi_max_points=20):
    if rsi_tiers is None:
        rsi_tiers = [((46, 55), 20), ((56, 65), 15), ((30, 45), 10), ((66, 75), 8)]
    if pd.isna(pdf["rsi"].iloc[-1]):
        return {"points": 0, "max_points": rsi_max_points, "reason": "RSI data not enough"}
    pdf["rsi"].iloc[-1] = float(pdf["rsi"].iloc[-1])
    for (lo, hi), pts in rsi_tiers:
        if lo <= pdf["rsi"].iloc[-1] <= hi:
            points = pts
            break
    else:
        points = 0
    return {"points": points,"max_points": rsi_max_points,"rsi": round(pdf["rsi"].iloc[-1], 1)}


# ============================================================
# 11. SCORE PRICE ACTION (LAYER 2E)
# ============================================================

def score_price_action(pdf, price_action_max_points=15):
    if len(pdf) < 4:
        return {"points": 0, "max_points": price_action_max_points, "reason": "price action data not enough"}
    last3 = pdf.iloc[-3:]
    lows = last3["Low"].values
    highs = last3["High"].values
    greens = last3["is_green"].values
    asc_count = int(lows[0] < lows[1]) + int(lows[1] < lows[2])
    desc_count = int(highs[0] > highs[1]) + int(highs[1] > highs[2])
    cur = pdf.iloc[-1]
    prev = pdf.iloc[-2]
    bearish_engulf = bool(prev["is_green"]) and (not bool(cur["is_green"])) and cur["Open"] >= prev["Close"] and cur["Close"] <= prev["Open"]
    is_doji = pd.notna(cur["body_ratio"]) and cur["body_ratio"] < 0.15
    last_bar_green = bool(greens[-1])
    if asc_count == 2 and bool(greens.all()):
        points = 15
        pattern = "3bar_higher_low_all_green"
    elif desc_count == 2 or bearish_engulf:
        points = 0
        pattern = "lower_high_or_bearish_engulfing"
    elif asc_count >= 1 and last_bar_green:
        points = 12
        pattern = "partial_higher_low_last_green"
    elif asc_count >= 1 and is_doji:
        points = 8
        pattern = "higher_low_with_doji"
    else:
        points = 3
        pattern = "no_clear_pattern"
    return {"points": points,"max_points": price_action_max_points,"pattern": pattern}





def get_action_label(total_score, mode="long"):
    if total_score >= 85:
        return {"tier": "85-100", "action": "Eksekusi penuh"}
    elif total_score >= 70:
        action_text = "Hati-hati — pastikan bid spread ≤1%" if mode == "scalp" else "Boleh entry, TP lebih dekat"
        return {"tier": "70-84", "action": action_text}
    elif total_score >= 55:
        action_text = "Mungkin kalau expert — disarankan skip" if mode == "scalp" else "Skip kecuali daily trend sangat bullish"
        return {"tier": "55-69", "action": action_text}
    else:
        return {"tier": "<55", "action": "Jangan entry"}

def scoring_technikal(
    pdf,daily_df,mode="long",max_score=100,min_score=70,
    trend_strong_pct=3,trend_moderate_pct=1,trend_at_sma_band_pct=1,trend_max_points=25,
    ema_slope_lookback=3,ema_flat_threshold_pct=0.15,entry_max_points=20,
    volume_lookback_bars=3,volume_tiers=None,volume_max_points=20,
    rsi_tiers=None,rsi_max_points=20,
    price_action_max_points=15
):
    indicators = {}
    indicators["trend_sma50"] = score_trend(daily_df, trend_strong_pct, trend_moderate_pct, trend_at_sma_band_pct, trend_max_points)
    indicators["entry_timing_ema"] = score_entry_timing(pdf, ema_slope_lookback, ema_flat_threshold_pct, entry_max_points)
    indicators["volume_spike"] = score_volume_spike(pdf, volume_lookback_bars, volume_tiers, volume_max_points)
    indicators["rsi_momentum"] = score_rsi(pdf, rsi_tiers, rsi_max_points)
    indicators["price_action"] = score_price_action(pdf, price_action_max_points)
    total_score = sum(v["points"] for v in indicators.values())
    passed = total_score >= min_score
    action_label = get_action_label(total_score, mode)
    return {"total_score": total_score,"max_score": max_score,"min_required": min_score,"passed": passed,"indicators": indicators,"action_label": action_label}

