"""
NEUROBRO SCALPING SYSTEM V3.0 — FULL DEBUG + CALIBRATION
Author: Neurobro
Data source: Yahoo Finance (yfinance)
"""

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

warnings.filterwarnings('ignore')

# ============================================================
# KONFIGURASI
# ============================================================
MIN_PRICE = 0
MAX_PRICE = 200
MIN_VOLUME_RATIO = 1.2
RSI_MIN = 45
RSI_MAX = 68
MIN_SCORE = 60
LOOKBACK_DAYS = 60

# LIST LENGKAP
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=f'{LOOKBACK_DAYS}d'):
    """Ambil data OHLCV dari yfinance."""
    try:
        stock = yf.Ticker(ticker)
        df = stock.history(period=period)
        if df.empty or len(df) < 20:
            return None, None
        df.columns = [c.lower() for c in df.columns]
        return df, stock
    except Exception as e:
        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()
    # Hindari division by zero
    avg_loss = avg_loss.replace(0, np.nan)
    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))
    return rsi


def check_higher_lows(df, lookback=10):
    """Cek apakah ada higher low dalam lookback hari."""
    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):
    """Cari support dan resistance dari 20 candle terakhir dan EMA50/200."""
    recent = df.tail(20)
    # Resistance: highest high dalam 20 hari
    resistance = recent['high'].max()
    # Support: lowest low dalam 20 hari
    support = recent['low'].min()
    
    # Juga cek EMA50 dan EMA200 sebagai support dinamis
    close = df['close']
    ema50 = calc_ema(close, 50)
    ema200 = calc_ema(close, 200)
    
    dynamic_support = support
    if not ema50.isna().all():
        ema50_val = ema50.iloc[-1]
        # EMA50 ada dalam range harga wajar
        if support <= ema50_val <= resistance:
            dynamic_support = max(support, ema50_val)
    
    return float(support), float(resistance)


def calc_atr(df, window=14):
    """Average True Range."""
    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 tr.tail(window).mean()


def analyze_stock(ticker):
    """Analisa saham dan return dict hasil."""
    df, stock = fetch_data(ticker)
    if df is None:
        return None

    close = df['close']
    volume = df['volume']
    high = df['high']
    low = df['low']

    # Data terbaru
    last_close = float(close.iloc[-1])
    last_volume = int(volume.iloc[-1])

    # ATR
    atr = float(calc_atr(df))

    # --- LAYER 1: ELIGIBILITY ---
    
    # 1. Harga dalam range yang ditentukan
    if last_close < MIN_PRICE or last_close > MAX_PRICE:
        return None

    # 2. Volume ratio dan minimal 2 data volume
    if len(volume) < 3:
        return None
    
    # Volume ratio: hari ini vs rata-rata 5 hari terakhir (bukan vs kemarin)
    avg_vol_5 = float(volume.tail(5).mean())
    vol_vs_avg = last_volume / avg_vol_5 if avg_vol_5 > 0 else 0
    
    # Volume ratio vs hari sebelumnya (buat spike detection)
    # vol_ratio_prev = last_volume / float(volume.iloc[-2]) if volume.iloc[-2] > 0 else 0
    
    # if vol_ratio_prev < MIN_VOLUME_RATIO and vol_vs_avg < 1.5:
    #     return None

    if vol_vs_avg < 1.2:   # Volume minimal 1.2x rata-rata 5 hari
        return None

    # 3. Cek kecukupan data untuk indikator
    if len(df) < 25:
        return None

    # --- HITUNG INDIKATOR ---
    
    # SMA50
    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/21
    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
    rsi = calc_rsi(close)
    rsi_val = 50.0
    if not rsi.isna().all():
        rsi_val = float(rsi.iloc[-1]) if not pd.isna(rsi.iloc[-1]) else 50.0

    # Support & Resistance
    support, resistance = get_support_resistance(df)
    
    # Spread
    spread_pct = (resistance - support) / last_close if last_close > 0 else 99

    # Higher lows
    hl_bool = check_higher_lows(df)

    # --- SCORING ---
    score = 0
    details = []

    # 1. SMA50 (20 pts)
    if sma50_trend == 'bullish':
        score += 20
        details.append(f'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(f'VOLUME: {vol_vs_avg:.1f}x avg — MASSIVE (+20)')
    elif vol_vs_avg >= 3:
        score += 15
        details.append(f'VOLUME: {vol_vs_avg:.1f}x avg — STRONG (+15)')
    elif vol_vs_avg >= 1.5:
        score += 10
        details.append(f'VOLUME: {vol_vs_avg:.1f}x avg — MODERATE (+10)')
    else:
        details.append(f'VOLUME: {vol_vs_avg:.1f}x avg (+0)')

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

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

    # 6. S/R spread (10 pts)
    if spread_pct <= 0.10:
        score += 10
        details.append(f'S/R SPREAD: {spread_pct:.1%} — TIGHT (+10)')
    elif spread_pct <= 0.20:
        score += 5
        details.append(f'S/R SPREAD: {spread_pct:.1%} — MODERATE (+5)')
    else:
        details.append(f'S/R SPREAD: {spread_pct:.1%} — WIDE (+0)')

    # --- FIBONACCI ENTRY ZONE ---
    range_hl = resistance - support
    entry_zone_low = support + range_hl * 0.236
    entry_zone_high = support + range_hl * 0.382

    # --- PREVIOUS DAY ---
    prev_high = float(high.iloc[-2]) if len(high) >= 2 else None
    prev_low = float(low.iloc[-2]) if len(low) >= 2 else None

    return {
        'ticker': ticker.replace('.JK', ''),
        'name': stock.info.get('longName', 'N/A')[:30] if stock and stock.info else ticker,
        'price': round(last_close, 0),
        'volume': last_volume,
        'vol_vs_avg': round(vol_vs_avg, 1),
        'rsi': round(rsi_val, 1),
        'atr': round(atr, 1),
        'score': score,
        'details': details,
        'support': round(support, 0),
        'resistance': round(resistance, 0),
        'entry_low': round(entry_zone_low, 0),
        'entry_high': round(entry_zone_high, 0),
        'prev_high': round(prev_high, 0) if prev_high else None,
        'prev_low': round(prev_low, 0) if prev_low else None,
    }


def run_screener(tickers=None):
    """Main function: jalankan screener dan print hasil."""
    if tickers is None:
        tickers = IDX_TICKERS
    
    print(f'{"="*62}')
    print(f'  NEUROBRO SCALPING SYSTEM V3.0 — FULLY DEBUGGED')
    print(f'  {datetime.now().strftime("%Y-%m-%d %H:%M")} WIB')
    print(f'{"="*62}')
    print(f'  Harga <= Rp{MAX_PRICE} | Vol ratio vs day-1 >= {MIN_VOLUME_RATIO}x')
    print(f'  RSI ideal: {RSI_MIN}-{RSI_MAX} | Penalty RSI > 70 & < 30')
    print(f'  Min Score: {MIN_SCORE}/100 | S/R spread filtering aktif')
    print(f'{"="*62}\n')

    results = []
    total = len(tickers)
    passed_count = 0
    failed_count = 0

    for i, ticker in enumerate(tickers, 1):
        print(f'  [{i:2d}/{total}] {ticker:12s} ', end='', flush=True)
        result = analyze_stock(ticker)
        if result:
            passed_count += 1
            results.append(result)
            icon = '✅❌'
            if result['rsi'] > 65:
                icon = '⚠️'
            elif result['score'] >= MIN_SCORE:
                icon = '✅'
            else:
                icon = '⚠️'
            
            extra = ''
            if result['rsi'] > 65:
                extra = f'\u26a0\ufe0f RSI {result["rsi"]}'
            elif 30 > result['rsi']:
                extra = f'\U0001f4c9 RSI {result["rsi"]}'
            elif result['score'] >= MIN_SCORE:
                extra = f'\u2714\ufe0f\u2714\ufe0f'
                print(f'PASS — SCORE {result["score"]:2d}/100 {extra}')
                continue

            print(f'PASS — SCORE {result["score"]:2d}/100 {extra}')
        else:
            failed_count += 1
            print(f'GAGAL ❌')

    results.sort(key=lambda x: x['score'], reverse=True)
    qualified = [r for r in results if r['score'] >= MIN_SCORE]
    
    print(f'\n{"="*62}')
    print(f'  HASIL SCREENING — {len(qualified)} saham lolos dari {total}')
    print(f'  ({passed_count} pass / {failed_count} fail)')
    print(f'{"="*62}\n')
    
    if not qualified:
        print('  Tidak ada saham yang lolos threshold.')
        print('  Kemungkinan: ')
        print('  - Hari ini pasar tutup (weekend/libur) — yfinance mungkin')
        print('    memberikan data yang tidak real-time')
        print('  - Semua saham di list memiliki RSI > 60 atau < 40')
        print('  - Volume overall sedang sepi')
        print()
        
        # Tampilkan top 5 saham dengan score tertinggi (meskipun < 70)
        if results:
            print(f'  TOP 5 terdekat (score < {MIN_SCORE}):')
            print(f'  {"TICKER":<7s} {"PRICE":>6s} {"VOL":>10s} {"RSI":>6s} {"SCORE":>6s}')
            print(f'  {"-"*7} {"-"*6} {"-"*10} {"-"*6} {"-"*6}')
            for r in results[:5]:
                vol_str = f'{r["volume"]:,}'
                print(f'  ${r["ticker"]:<5s} {r["price"]:>5.0f} {vol_str:>10s} {r["rsi"]:>5.1f} {r["score"]:>4d}')
            print()
        
        return qualified

    # Tampilkan hasil untuk yang lolos
    print(f'  {"TICKER":<7s} {"PRICE":>6s} {"VOL":>10s} {"VOL/MA":>7s} {"RSI":>6s} {"ATR":>6s} {"SCORE":>6s}')
    print(f'  {"-"*7} {"-"*6} {"-"*10} {"-"*7} {"-"*6} {"-"*6} {"-"*6}')
    
    for r in qualified:
        vol_str = f'{r["volume"]:,}'
        print(f'  ${r["ticker"]:<5s} {r["price"]:>5.0f} {vol_str:>10s} {r["vol_vs_avg"]:>5.1f}x {r["rsi"]:>5.1f} {r["atr"]:>5.0f} {r["score"]:>4d}')
    
    print(f'\n{"="*62}')
    print(f'  ENTRY ZONE — TOP {min(3, len(qualified))}')
    print(f'{"="*62}\n')
    
    for r in qualified[:3]:
        entry_zone = f'Rp{r["entry_low"]:,.0f} - Rp{r["entry_high"]:,.0f}'
        sl = f'Rp{max(r["entry_low"] - r["atr"] * 1.5, r["support"]) :,.0f}'
        tp1 = f'Rp{min(r["price"] + r["atr"] * 2, r["resistance"]) :,.0f}'
        tp2 = f'Rp{r["resistance"] :,.0f}'
        
        print(f'  ${r["ticker"]} — Rp{r["price"]:,.0f} | Score: {r["score"]}/100')
        print(f'  Entry: {entry_zone}')
        print(f'  SL: {sl} | TP1: {tp1} | TP2: {tp2}')
        print()

    print(f'{"="*62}')
    print(f'  SELESAI — {len(qualified)} scalp kandidat ditemukan')
    print(f'{"="*62}')
    
    return qualified


# ============================================================
# DEBUGGING DIAGNOSTICS — untuk liat kenapa saham tertentu gagal
# ============================================================
def debug_ticker(ticker):
    """Debug satu ticker untuk lihat kenapa gagal."""
    print(f'\n{"="*52}')
    print(f'  DEBUG: {ticker}')
    print(f'{"="*52}')
    
    df, stock = fetch_data(ticker)
    if df is None:
        print('  ❌ Tidak dapat fetch data')
        return
    
    close = df['close']
    volume = df['volume']
    high = df['high']
    low = df['low']
    
    last_close = float(close.iloc[-1])
    last_volume = int(volume.iloc[-1])
    
    print(f'  Harga terakhir: Rp{last_close:,.0f}')
    print(f'  Volume: {last_volume:,}')
    print(f'  MAX_PRICE check: {last_close <= MAX_PRICE} ({last_close} <= {MAX_PRICE})')
    print(f'  MIN_PRICE check: {last_close >= MIN_PRICE} ({last_close} >= {MIN_PRICE})')
    
    # Volume ratio
    avg_vol_5 = float(volume.tail(5).mean())
    vol_vs_avg = last_volume / avg_vol_5 if avg_vol_5 > 0 else 0
    print(f'  Volume vs 5-day avg: {vol_vs_avg:.1f}x')
    
    vol_prev = float(volume.iloc[-2]) if len(volume) > 1 else 0
    vol_ratio = last_volume / vol_prev if vol_prev > 0 else 0
    print(f'  Volume ratio (day-1): {vol_ratio:.1f}x')
    print(f'  VOL_RATIO check: {vol_ratio >= MIN_VOLUME_RATIO or vol_vs_avg >= 1.5}')
    
    # RSI
    rsi = calc_rsi(close)
    if not rsi.isna().all():
        rsi_val = float(rsi.iloc[-1])
        print(f'  RSI: {rsi_val:.1f}')
    else:
        print(f'  RSI: NaN')
    
    # Support/Resistance
    support, resistance = get_support_resistance(df)
    spread = (resistance - support) / last_close * 100
    print(f'  Support: Rp{support:,.0f} | Resistance: Rp{resistance:,.0f} | Spread: {spread:.1f}%')
    
    # SMA50
    sma50 = calc_sma(close, 50)
    if not sma50.isna().all() and not pd.isna(sma50.iloc[-1]):
        print(f'  SMA50: Rp{sma50.iloc[-1]:,.0f} | Price > SMA50: {last_close > sma50.iloc[-1]}')
    
    # Length
    print(f'  Data points: {len(df)}')
    print(f'  Cukup 25 data: {len(df) >= 25}')
    
    print(f'{"="*52}\n')


# ============================================================
# FUNGSI UPDATE TICKER LIST (jika perlu tambah sendiri)
# ============================================================
def add_tickers(new_tickers):
    """Tambah ticker ke list global."""
    global IDX_TICKERS
    for t in new_tickers:
        t_clean = t.upper().strip()
        if not t_clean.endswith('.JK'):
            t_clean += '.JK'
        if t_clean not in IDX_TICKERS:
            IDX_TICKERS.append(t_clean)
            print(f'  ✅ Ditambahkan: {t_clean}')
        else:
            print(f'  ⚠️ Sudah ada: {t_clean}')


# ============================================================
# JALANKAN
# ============================================================
if __name__ == '__main__':
    # Coba debug dulu $BIPI dan $AHAP
    debug_ticker('BIPI.JK')
    debug_ticker('AHAP.JK')
    
    # Jalankan screener
    results = run_screener()