import os
import sys
import time
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

import requests
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib
matplotlib.use("Agg")  # biar bisa jalan tanpa display/GUI (server/headless)
import matplotlib.lines as mlines
import mplfinance as mpf

# AlphaX Pattern Detection
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
    from alphax import detect_all_bullish_patterns
    ALPHAX_AVAILABLE = True
except ImportError as e:
    print(f"[WARN] AlphaX module tidak tersedia: {e}")
    ALPHAX_AVAILABLE = False

# ============ KONFIGURASI ============
TELEGRAM_BOT_TOKEN = "ISI_TOKEN_BOT_TELEGRAM_KAMU"
TELEGRAM_CHAT_ID = "ISI_CHAT_ID_KAMU"

TICKER_FILE = "tickers_idx.txt"

MIN_VOLUME = 100_000     # filter saham terlalu tipis transaksinya
BATCH_SIZE = 50
BATCH_DELAY_SEC = 2

# --- TIMEFRAME ---
# "1h"  = candle per jam, trend filter pakai SMA200 (~1 bulan lebih data jam).
#         Catatan: makin pendek timeframe, makin banyak sinyal tapi makin
#         "berisik" (noise) - cocok kalau kamu mau screening lebih sering,
#         tapi tetap bukan scalping selama MIN_SCORE & filter likuiditas dijaga ketat.
# "1d"  = candle harian, trend filter pakai SMA200 (cocok untuk swing mingguan-bulanan)
# "1wk" = candle mingguan, trend filter pakai SMA50 (~1 tahun), cocok posisi lebih panjang
TIMEFRAME = "1d"

TIMEFRAME_CONFIG = {
    "1h":  {"interval": "60m", "period": "3mo", "trend_sma": 200},
    "1d":  {"interval": "1d",  "period": "2y", "trend_sma": 200},
    "1wk": {"interval": "1wk", "period": "5y", "trend_sma": 50},
}

# --- Bobot skor tiap indikator (bisa kamu tuning) ---
SCORE_RSI_OVERSOLD = 2
SCORE_MACD_CROSSOVER = 2
SCORE_ABOVE_TREND_SMA = 2
SCORE_NEAR_BB_LOWER = 1
SCORE_VOLUME_SPIKE = 1
SCORE_STOCH_RSI_BULLISH = 2   # Stochastic RSI oversold + baru cross naik
SCORE_MA_GOLDEN_CROSS = 1     # MA cepat (20) baru cross naik MA lambat (50)
SCORE_ALPHAX_PATTERN = 5      # AlphaX pattern detection (max bonus dari confluence score)

MAX_POSSIBLE_SCORE = (SCORE_RSI_OVERSOLD + SCORE_MACD_CROSSOVER + SCORE_ABOVE_TREND_SMA
                       + SCORE_NEAR_BB_LOWER + SCORE_VOLUME_SPIKE
                       + SCORE_STOCH_RSI_BULLISH + SCORE_MA_GOLDEN_CROSS
                       + SCORE_ALPHAX_PATTERN)

MIN_SCORE = 5   # minimal skor total biar masuk hasil (dari maks MAX_POSSIBLE_SCORE)
RSI_THRESHOLD = 30
BB_PROXIMITY = 1.02   # dianggap "dekat lower band" kalau close <= lower_band * 1.02
VOLUME_SPIKE_MULT = 1.5

STOCH_RSI_PERIOD = 14
STOCH_SMOOTH_K = 3
STOCH_SMOOTH_D = 3
STOCH_OVERSOLD = 20    # StochRSI %K di bawah ini dianggap oversold (skala 0-100)

MA_FAST_PERIOD = 20
MA_SLOW_PERIOD = 50

# --- AlphaX Pattern Detection Configuration ---
ALPHAX_ENABLED = True
ALPHAX_MIN_CONFLUENCE = 5  # Minimum confluence score (0-10)
ALPHAX_HTF_TIMEFRAME = "1wk"  # Higher timeframe untuk bias check (1wk untuk daily chart)
ALPHAX_HTF_FAST_EMA = 21
ALPHAX_HTF_SLOW_EMA = 55
ALPHAX_CHOP_MAX = 62.0  # Chop index threshold (di atas ini = choppy, avoid)
ALPHAX_PIVOT_LEFT_LB = 10
ALPHAX_PIVOT_RIGHT_LB = 10
ALPHAX_MAX_PIVOTS = 500
ALPHAX_MIN_RR = 1.5  # Minimum Risk:Reward ratio

# --- FILTER LIKUIDITAS & FREE FLOAT ---
# Ini FILTER KERAS (hard filter) - bukan penambah skor, tapi syarat wajib
# supaya saham layak dianalisa sama sekali (saham gak likuid/free float kecil
# gampang digoreng & susah exit posisi walau sinyal teknikalnya bagus).
MIN_AVG_VALUE_TRADED = 1_000_000_000   # rata-rata nilai transaksi 20 hari (Rupiah), pakai data harga yang sudah ada
MIN_FREE_FLOAT_PCT = 25                # minimal free float (%), dari cache free_float_idx.csv
FREE_FLOAT_FILE = "free_float_idx.csv"
# Kalau True: saham TANPA data free float di cache otomatis di-skip (aman, tapi
#             bisa buang banyak saham kalau cache belum lengkap).
# Kalau False: saham tanpa data free float TETAP diproses (filter ini dilewati
#              khusus buat saham itu), cocok dipakai sebelum cache lengkap.
REQUIRE_FREE_FLOAT_DATA = False

# --- JADWAL: kapan robot boleh jalan ---
RUN_DAYS = [0, 1, 2, 3, 4]     # 0=Senin ... 4=Jumat
RUN_HOUR_START = 9             # jam mulai boleh screening (WIB)
RUN_HOUR_END = 16              # jam terakhir boleh screening (WIB)
ENFORCE_SCHEDULE = True

# --- SELF-LOOP MODE ---
# True  = script jalan terus sebagai proses lokal, otomatis screening
#         tiap jam bulat (13:00, 14:00, dst) selama masih di jendela
#         RUN_HOUR_START-RUN_HOUR_END, tanpa perlu cron/scheduler eksternal.
# False = jalan sekali lalu exit (dipakai kalau kamu tetap mau pakai cron).
SELF_LOOP = True

# --- CHART OTOMATIS ---
CHARTS_DIR = "charts"           # folder lokal buat simpan PNG
SEND_CHART_TO_TELEGRAM = True   # kirim chart sebagai foto ke Telegram juga
MAX_CHARTS_PER_RUN = 15         # batasi biar gak spam Telegram kalau hasil banyak
CHART_LOOKBACK_CANDLES = 90     # berapa candle terakhir yang ditampilkan di chart
# =======================================


def is_within_schedule() -> bool:
    if not ENFORCE_SCHEDULE:
        return True
    # now = datetime.now(ZoneInfo("Asia/Jakarta"))
    now = datetime.now(ZoneInfo("Asia/Jakarta")) - timedelta(hours=7)
    if now.weekday() not in RUN_DAYS:
        print(f"[SKIP] Hari ini ({now.strftime('%A')}) di luar RUN_DAYS.")
        return False
    if not (RUN_HOUR_START <= now.hour < RUN_HOUR_END):
        print(f"[SKIP] Jam sekarang ({now.hour}:00 WIB) di luar jadwal.")
        return False
    return True


def load_tickers() -> list[str]:
    with open(TICKER_FILE) as f:
        codes = [line.strip() for line in f if line.strip()]
    return [f"{code}.JK" for code in codes]


def load_free_float() -> dict:
    """
    Baca cache free_float_idx.csv (dari update_free_float.py).
    Return dict {kode_saham: persen_free_float}. Kalau file belum ada,
    return dict kosong (semua saham diperlakukan 'data gak tersedia').
    """
    if not os.path.exists(FREE_FLOAT_FILE):
        print(f"[WARN] {FREE_FLOAT_FILE} belum ada - jalankan update_free_float.py dulu "
              f"kalau mau filter free float aktif penuh.")
        return {}

    free_float_map = {}
    with open(FREE_FLOAT_FILE) as f:
        next(f)  # skip header
        for line in f:
            parts = line.strip().split(",")
            if len(parts) == 2 and parts[1]:
                try:
                    free_float_map[parts[0]] = float(parts[1])
                except ValueError:
                    continue
    return free_float_map


# ============ INDIKATOR ============

def calc_rsi(close: pd.Series, period: int = 14) -> pd.Series:
    delta = close.diff()
    gain = delta.clip(lower=0)
    loss = -delta.clip(upper=0)
    avg_gain = gain.ewm(alpha=1 / period, min_periods=period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1 / period, min_periods=period, adjust=False).mean()
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))


def calc_macd(close: pd.Series, fast=12, slow=26, signal=9):
    ema_fast = close.ewm(span=fast, adjust=False).mean()
    ema_slow = close.ewm(span=slow, adjust=False).mean()
    macd_line = ema_fast - ema_slow
    signal_line = macd_line.ewm(span=signal, adjust=False).mean()
    histogram = macd_line - signal_line
    return macd_line, signal_line, histogram


def calc_bollinger(close: pd.Series, period=20, num_std=2):
    sma = close.rolling(window=period).mean()
    std = close.rolling(window=period).std()
    upper = sma + num_std * std
    lower = sma - num_std * std
    return sma, upper, lower


def calc_stochastic_rsi(rsi: pd.Series, period=STOCH_RSI_PERIOD,
                         smooth_k=STOCH_SMOOTH_K, smooth_d=STOCH_SMOOTH_D):
    """
    Stochastic RSI: 'stochastic dari RSI' - lebih sensitif daripada RSI biasa,
    bagus buat nangkep titik balik oversold/overbought yang lebih presisi.
    Skala 0-100 (bukan 0-1) biar konsisten sama style StochRSI di TradingView.
    """
    min_rsi = rsi.rolling(window=period).min()
    max_rsi = rsi.rolling(window=period).max()
    stoch = (rsi - min_rsi) / (max_rsi - min_rsi) * 100
    k = stoch.rolling(window=smooth_k).mean()
    d = k.rolling(window=smooth_d).mean()
    return k, d


def analyze(df: pd.DataFrame, trend_sma_period: int, ticker: str = None,
            free_float_map: dict = None) -> dict | None:
    
    return


def screen_all(tickers: list[str], timeframe: str, free_float_map: dict) -> list[dict]:
    cfg = TIMEFRAME_CONFIG[timeframe]
    all_results = []
    batches = list(chunk(tickers, BATCH_SIZE))

    for i, batch in enumerate(batches, 1):
        print(f"[INFO] Batch {i}/{len(batches)} ({len(batch)} ticker)...")
        all_results.extend(screen_batch(batch, cfg["interval"], cfg["period"], cfg["trend_sma"], free_float_map))
        if i < len(batches):
            time.sleep(BATCH_DELAY_SEC)

    return sorted(all_results, key=lambda x: (-x["score"], x["rsi"]))


def run_screening():
    tickers = load_tickers()
    free_float_map = load_free_float()
    print(f"[INFO] Scanning {len(tickers)} saham, timeframe={TIMEFRAME}, min_score={MIN_SCORE}, "
          f"free_float_data={len(free_float_map)} saham, min_avg_value={MIN_AVG_VALUE_TRADED:,.0f}...")

    results = screen_all(tickers, TIMEFRAME, free_float_map)

    if SEND_CHART_TO_TELEGRAM and results:
        sent = 0
        for r in results:
            if sent >= MAX_CHARTS_PER_RUN:
                print(f"[INFO] Sudah kirim {MAX_CHARTS_PER_RUN} chart, sisanya cek folder {CHARTS_DIR}/ lokal.")
                break
            if r.get("chart_path"):
                caption = f"`{r['ticker']}` — Score {r['score']}/{MAX_POSSIBLE_SCORE} | RSI {r['rsi']} | Close {r['close']}"
                # send_telegram_photo(r["chart_path"], caption)
                sent += 1

    print(f"[DONE] {len(results)} saham lolos filter. Chart tersimpan di folder {CHARTS_DIR}/.")


def main_once():
    """Jalan sekali lalu exit. Cocok dipakai kalau kamu pakai cron."""
    if not is_within_schedule():
        return
    run_screening()


def seconds_until_next_hour_mark() -> float:
    """Hitung berapa detik lagi sampai jam bulat berikutnya (contoh: sekarang 13:23 -> tunggu sampai 14:00)."""
    now = datetime.now(ZoneInfo("Asia/Jakarta"))
    next_hour = (now.replace(minute=0, second=0, microsecond=0)
                 + pd.Timedelta(hours=1))
    return (next_hour - now).total_seconds()


def main_loop():
    """
    Self-loop lokal: jalan terus di terminal, screening otomatis TEPAT
    tiap jam bulat (13:00, 14:00, 15:00, dst), bukan interval ngambang
    dari kapan pun kamu start script-nya.

    Catatan buat local run:
    - Selama terminal/proses ini kebuka, dia jalan terus.
    - Kalau laptop sleep/mati atau terminal ditutup, prosesnya berhenti
      (beda dengan VPS yang nyala 24/7) - tinggal jalankan ulang kalau gitu.
    """
    print(f"[INFO] Self-loop aktif (align ke jam bulat). "
          f"Jendela jalan: {RUN_HOUR_START}:00-{RUN_HOUR_END}:00 WIB, hari {RUN_DAYS}.")

    while True:
        # now = datetime.now(ZoneInfo("Asia/Jakarta"))
        now = datetime.now(ZoneInfo("Asia/Jakarta")) - timedelta(hours=7)
        if is_within_schedule():
            try:
                run_screening()
            except Exception as e:
                print(f"[ERROR] Screening gagal: {e}")
        else:
            print(f"[IDLE] {now.strftime('%Y-%m-%d %H:%M:%S')} WIB - di luar jendela jadwal.")

        sleep_sec = seconds_until_next_hour_mark()
        next_run = now + pd.Timedelta(seconds=sleep_sec)
        print(f"[INFO] Tidur sampai {next_run.strftime('%H:%M:%S')} WIB ({int(sleep_sec)}s)...")
        time.sleep(sleep_sec)


if __name__ == "__main__":
    if SELF_LOOP:
        main_loop()
    else:
        main_once()