"""
NEUROBRO SCALPING SYSTEM V4.0 — FULL BUMBU
Dual approach: Fibonacci entry + Volume confirmation
"""

import yfinance as yf
import pandas as pd
import numpy as np
import warnings
from datetime import datetime

warnings.filterwarnings('ignore')

# ============================================================
# KONFIGURASI — REALISTIC FOR IDX SCALP
# ============================================================
MIN_PRICE = 0                 # Disesuaikan ke 50 karena Rp 0 adalah saham gocap mati di IDX
MAX_PRICE = 200               # Dinaikkan agar saham likuid seperti BBRI/TLKM bisa lolos filter harga
MIN_VOLUME_AVG_RATIO = 1.2     # Volume vs 5-day average
RSI_MIN = 40
RSI_MAX = 70                   # Dinaikin lagi — 68 masih kurang
MIN_SCORE = 60
LOOKBACK_DAYS = 366             # Naikin jadi 90 biar data SMA50 valid

IDX_TICKERS = [
    'BIPI.JK', 'AHAP.JK', 'KOKA.JK', 'BUVA.JK', 'HOPE.JK',
    'KBLV.JK', 'KJEN.JK', 'MLPL.JK', 'SQMI.JK', 'COCO.JK',
    'KIJA.JK', 'TOOL.JK', 'BBYB.JK', 'BUMI.JK', 'FUTR.JK',
    'PADI.JK', 'KPIG.JK', 'KOTA.JK', 'ISEA.JK', 'IKAN.JK',
    'BKDP.JK', 'FIRE.JK', 'RGAS.JK', 'MEDC.JK', 'CUAN.JK',
    'MPOW.JK', 'PIPA.JK', 'DATA.JK', 'KIOS.JK', 'RODA.JK',
    'PGEO.JK', 'DSSA.JK', 'RAJA.JK', 'ARTO.JK', 'FWCT.JK',
    'AYAM.JK', 'BBRI.JK', 'TLKM.JK', 'ASII.JK', 'LAND.JK',
]


def fetch_data(ticker, period="3mo"):
    try:
        stock = yf.Ticker(ticker)
        df = stock.history(period=period)
        if df.empty or len(df) < 30:
            return None, None
        df.columns = [c.lower() for c in df.columns]
        return df, stock
    except Exception:
        return None, None


def calc_sma(series, window):
    return series.rolling(window=window).mean()


def calc_ema(series, window):
    return series.ewm(span=window, adjust=False).mean()


def calc_rsi(series, window=14):
    delta = series.diff()
    gain = delta.where(delta > 0, 0.0)
    loss = (-delta.where(delta < 0, 0.0))
    avg_gain = gain.rolling(window=window).mean()
    avg_loss = loss.rolling(window=window).mean().replace(0, np.nan)
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))


def check_higher_lows(df, lookback=10):
    if len(df) < lookback + 5:
        return False
    recent = df.tail(lookback)
    low = recent['low'].values
    first_half_min = np.min(low[:lookback // 2])
    second_half_min = np.min(low[lookback // 2:])
    return second_half_min > first_half_min


def get_support_resistance(df):
    recent = df.tail(20)
    resistance = recent['high'].max()
    support = recent['low'].min()
    
    close = df['close']
    ema50 = calc_ema(close, 50)
    
    if not ema50.isna().all() and not pd.isna(ema50.iloc[-1]):
        ema50_val = ema50.iloc[-1]
        if support <= ema50_val <= resistance:
            support = max(support, ema50_val)
            
    return float(support), float(resistance)


def calc_atr(df, window=14):
    high, low, close = df['high'], df['low'], df['close']
    tr1 = high - low
    tr2 = (high - close.shift()).abs()
    tr3 = (low - close.shift()).abs()
    tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
    return float(tr.tail(window).mean())


def get_fib_levels(support, resistance):
    """Return Fibonacci retracement levels dari range S/R."""
    diff = resistance - support
    if diff == 0:
        return {'0.236': support, '0.382': support, '0.5': support, '0.618': support}
    return {
        '0.236': support + (0.236 * diff),
        '0.382': support + (0.382 * diff),
        '0.5': support + (0.5 * diff),
        '0.618': support + (0.618 * diff)
    }


def get_orderflow_style_entry(price, fib_zone_low, fib_zone_high, support):
    """
    BUMBU RAHASIA #1: Order-flow style entry
    - Kalo harga dekat support (< 3% dari support): masuk order-flow style, 
      beli di atas support (konfirmasi) 
    - Kalo harga di tengah range: pake Fibonacci
    """
    pct_from_support = (price - support) / support if support > 0 else 0
    
    if pct_from_support <= 0.03:
        conservative_entry = max(support + price * 0.01, price * 0.98)
        return round(conservative_entry, 0), round(price * 1.01, 0)
    else:
        return round(fib_zone_low, 0), round(fib_zone_high, 0)


def get_sl_scalp(entry_low, atr, price, support):
    """
    BUMBU RAHASIA #2: SL capping 8%
    """
    sl_atr = entry_low - atr * 1.5
    sl_capped = price * 0.92  # -8% max
    sl_support = support * 0.98  # sedikit di bawah support
    
    return round(max(sl_atr, sl_capped, sl_support), 0)


def get_tp_scalp(price, atr, resistance, style='conservative'):
    """
    BUMBU RAHASIA #3: TP styling
    conservative = ATR-based (lebih dekat)
    aggressive = resistance-based (lebih jauh)
    """
    if style == 'conservative':
        tp1 = min(price + atr * 1.5, resistance)
        tp2 = min(price + atr * 2.5, resistance)
    else:
        tp1 = min(price + atr * 2, resistance)
        tp2 = resistance
    
    return round(tp1, 0), round(tp2, 0)


def analyze_stock(ticker, tp_style='conservative'):
    df, stock = fetch_data(ticker)
    if df is None:
        return None

    close = df['close']
    volume = df['volume']

    last_close = float(close.iloc[-1])
    last_volume = int(volume.iloc[-1])
    
    # Filter harga
    if last_close < MIN_PRICE or last_close > MAX_PRICE:
        return None

    # Volume filter — 5-day average
    avg_vol_5 = float(volume.tail(5).mean())
    vol_vs_avg = last_volume / avg_vol_5 if avg_vol_5 > 0 else 0
    if vol_vs_avg < MIN_VOLUME_AVG_RATIO:
        return None

    atr = calc_atr(df)

    # Indikator
    sma50 = calc_sma(close, 50)
    sma50_trend = None
    if not sma50.isna().all() and not pd.isna(sma50.iloc[-1]):
        sma50_trend = 'bullish' if last_close > sma50.iloc[-1] else 'bearish'

    ema9 = calc_ema(close, 9)
    ema21 = calc_ema(close, 21)
    ema_cross = None
    if not ema9.isna().all() and not ema21.isna().all():
        if not pd.isna(ema9.iloc[-1]) and not pd.isna(ema21.iloc[-1]):
            ema_cross = 'golden' if ema9.iloc[-1] > ema21.iloc[-1] else 'death'

    rsi = calc_rsi(close)
    rsi_val = 50.0
    if not rsi.isna().all() and not pd.isna(rsi.iloc[-1]):
        rsi_val = float(rsi.iloc[-1])

    support, resistance = get_support_resistance(df)
    fib = get_fib_levels(support, resistance)
    spread_pct = (resistance - support) / last_close if last_close > 0 else 99
    hl_bool = check_higher_lows(df)

    # --- SCORING (MAX 100) ---
    score = 0
    details = []

    # 1. SMA50 (20 pts)
    if sma50_trend == 'bullish':
        score += 20
        details.append('SMA50: BULLISH (+20)')
    elif sma50_trend == 'bearish':
        details.append('SMA50: BEARISH (+0)')
    else:
        details.append('SMA50: N/A')

    # 2. EMA9/21 (15 pts)
    if ema_cross == 'golden':
        score += 15
        details.append('EMA9/21: GOLDEN CROSS (+15)')
    elif ema_cross == 'death':
        details.append('EMA9/21: DEATH CROSS (+0)')
    else:
        details.append('EMA9/21: N/A')

    # 3. Volume (20 pts)
    if vol_vs_avg >= 5:
        score += 20
        details.append('VOLUME: — MASSIVE (+20)')
    elif vol_vs_avg >= 3:
        score += 15
        details.append('VOLUME: — STRONG (+15)')
    elif vol_vs_avg >= 1.5:
        score += 10
        details.append('VOLUME: — MODERATE (+10)')
    else:
        details.append('VOLUME: — THIN (+0)')

    # 4. RSI (25 pts) + PENALTI
    if RSI_MIN <= rsi_val <= RSI_MAX:
        score += 25
        details.append('RSI: — SWEET SPOT (+25)')
    elif 30 <= rsi_val < RSI_MIN:
        score += 10
        details.append('RSI: — UNDERBOUGHT (+10)')
    elif RSI_MAX < rsi_val <= 75:
        score += 5
        details.append('RSI: — WARM (+5)')
    elif rsi_val > 75:
        score -= 15
        details.append('RSI: — OVERBOUGHT! (-15)')
    elif rsi_val < 30:
        score -= 10
        details.append('RSI: — OVERSOLD EXTREME (-10)')
    else:
        details.append('RSI: (+0)')

    # 5. Price action (10 pts)
    if hl_bool:
        score += 10
        details.append('HIGHER LOW (+10)')
    else:
        details.append('NO HIGHER LOW (+0)')

    # 6. S/R spread (10 pts)
    if spread_pct <= 0.08:
        score += 10
        details.append('SPREAD: — TIGHT (+10)')
    elif spread_pct <= 0.15:
        score += 5
        details.append('SPREAD: — MODERATE (+5)')
    else:
        details.append('SPREAD: — WIDE (+0)')

    # --- BUMBU RAHASIA ENTRY ---
    entry_low_fib = fib['0.236']
    entry_high_fib = fib['0.382']
    
    entry_low, entry_high = get_orderflow_style_entry(
        last_close, entry_low_fib, entry_high_fib, support
    )
    
    sl = get_sl_scalp(entry_low, atr, last_close, support)
    tp1, tp2 = get_tp_scalp(last_close, atr, resistance, tp_style)
    
    risk = entry_low - sl if entry_low > sl else 1
    reward = tp1 - entry_low
    rr = round(reward / risk, 2) if risk > 0 else 0

    return {
        'ticker': ticker,
        'score': score,
        'last_close': last_close,
        'vol_vs_avg': vol_vs_avg,
        'rsi': rsi_val,
        'entry_low': entry_low,
        'entry_high': entry_high,
        'sl': sl,
        'tp1': tp1,
        'tp2': tp2,
        'rr': rr,
        'details': details
    }


def run_screener(tickers=None, tp_style='conservative'):
    if tickers is None:
        tickers = IDX_TICKERS
    
    print("==============================================================")
    print("      NEUROBRO SCALP SYSTEM V4.0 — FULL BUMBU")
    print(f"      RUN TIME: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("==============================================================")
    print(f"Min Score Filter: {MIN_SCORE}")
    print(f"TP Style: {tp_style.upper()}")
    print("Scanning tickers...")

    results = []
    
    for ticker in tickers:
        result = analyze_stock(ticker, tp_style)
        if result:
            results.append(result)
            extra = ''
            if result['rsi'] > 70:
                extra = ' ⚠️ RSI HIGH'
            elif result['score'] >= MIN_SCORE:
                extra = ' ✔️✔️'
            
            bar_len = 20
            filled = int(max(0, min(result['score'], 100)) / 100 * bar_len)
            bar = '█' * filled + '░' * (bar_len - filled)
            
            print(f"[{ticker:<9}] Score: {result['score']:3d} [{bar}] {extra}")
        else:
            print(f"[{ticker:<9}] SKIP (Tidak memenuhi filter awal / data kurang)")

    results.sort(key=lambda x: x['score'], reverse=True)
    qualified = [r for r in results if r['score'] >= MIN_SCORE]
    
    print("\n" + "="*62)
    print(f" HASIL SCREENING — LOLOS FILTER (SCORE >= {MIN_SCORE})")
    print("="*62)
    
    if not qualified:
        print(' Tidak ada saham yang lolos target score. Top 3 mendekati:')
        for r in results[:3]:
            print(f"  - {r['ticker']}: Score {r['score']} (Last: Rp {r['last_close']})")
        return qualified

    # HEADER TABLE
    print(f"{'TICKER':<9} | {'SCORE':<5} | {'PRICE':<6} | {'VOL RATIO':<9} | {'RSI':<5} | {'ENTRY ZONE':<13} | {'R:R':<4}")
    print("-" * 65)
    for r in qualified:
        entry_zone = f"{int(r['entry_low'])}-{int(r['entry_high'])}"
        print(f"{r['ticker']:<9} | {r['score']:<5d} | {int(r['last_close']):<6d} | {r['vol_vs_avg']:<9.2f} | {r['rsi']:<5.1f} | {entry_zone:<13} | {r['rr']:<4.2f}")

    print("\n" + "="*62)
    print(" DETAIL STRATEGI TOP 3 KANDIDAT")
    print("="*62)
    
    for r in qualified[:3]:
        print(f"▶️ {r['ticker']} (Score: {r['score']}/100) — Last Price: Rp {int(r['last_close'])}")
        print(f"  • Entry Zone : Rp {int(r['entry_low'])} - Rp {int(r['entry_high'])}")
        print(f"  • Stop Loss  : Rp {int(r['sl'])}")
        print(f"  • Target 1   : Rp {int(r['tp1'])} | Target 2: Rp {int(r['tp2'])}")
        print(f"  • Risk/Reward: 1 : {r['rr']}")
        print("  • Breakdown  :")
        for det in r['details']:
            print(f"    - {det}")
        print()

    print("========================= SELESAI =========================")
    return qualified


if __name__ == '__main__':
    run_screener(tp_style='conservative')