#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json, os, re, sys, datetime
import yfinance as yf

BASE = os.path.expanduser("~/Claude/Projects/에이전트구축")
MEDIA = os.path.join(BASE, "media_feed.jsonl")
XFEED = os.path.join(BASE, "x_feed.jsonl")
REJECT = os.path.join(BASE, "rejected_feed.jsonl")
GAPLOG = os.path.join(BASE, "gap_log.jsonl")
NQLEV = os.path.join(BASE, "nq_levels.py")
DRY = "--dry" in sys.argv
THRESH = 5.0

WATCH = {
    "MU": "마이크론", "SNDK": "샌디스크", "NBIS": "네비우스", "AAOI": "AAOI",
    "SOXL": "SOXL", "NVDA": "엔비디아", "META": "메타", "LITE": "루멘텀",
    "TER": "테라다인", "ALAB": "아스테라", "COHR": "코히런트", "MRVL": "마벨",
    "CRDO": "크레도", "GLW": "코닝", "AVGO": "브로드컴", "CIEN": "시에나",
    "AEHR": "AEHR", "ASML": "ASML", "LRCX": "램리서치", "AMAT": "AMAT",
    "KLAC": "KLA", "AMD": "AMD", "INTC": "인텔", "TSM": "TSMC",
    "IBM": "IBM", "NOW": "서비스나우", "CRM": "세일즈포스", "ACN": "액센츄어",
    "JPM": "JPM", "MSFT": "MSFT", "GOOGL": "구글", "AMZN": "아마존",
    "ORCL": "오라클", "CRWV": "코어위브", "IREN": "IREN",
}

def creds():
    try:
        s = open(NQLEV, encoding="utf-8").read()
        return (re.search(r'BOT_TOKEN\s*=\s*["\']([^"\']+)', s).group(1),
                re.search(r'CHAT_ID\s*=\s*["\']?([^"\'\s]+)', s).group(1))
    except Exception:
        return None, None

def send(msg):
    import urllib.request, urllib.parse
    tok, cid = creds()
    if not tok:
        print("[!] 토큰 없음"); return
    d = urllib.parse.urlencode({"chat_id": cid, "text": msg,
                                "disable_web_page_preview": "true"}).encode()
    urllib.request.urlopen(f"https://api.telegram.org/bot{tok}/sendMessage", d, timeout=15)

def feed_text(days=2):
    now = datetime.datetime.now(datetime.timezone.utc)
    buf = []
    for p in (MEDIA, XFEED, REJECT):
        if not os.path.exists(p): continue
        for line in open(p, "rb"):
            try: d = json.loads(line.decode("utf-8", "replace"))
            except Exception: continue
            ts = d.get("ts") or d.get("pub") or ""
            try: t = datetime.datetime.fromisoformat(str(ts).replace("Z", "+00:00"))
            except Exception: continue
            if t.tzinfo is None: t = t.replace(tzinfo=datetime.timezone.utc)
            if (now - t).days <= days:
                buf.append((d.get("text", ""), p.endswith("rejected_feed.jsonl")))
    return buf

def main():
    px = yf.download(list(WATCH), period="5d", interval="1d",
                     progress=False, auto_adjust=False)["Close"]
    moves = {}
    for tk in WATCH:
        try:
            s = px[tk].dropna()
            if len(s) < 2: continue
            ch = (s.iloc[-1] / s.iloc[-2] - 1) * 100
            if abs(ch) >= THRESH: moves[tk] = round(float(ch), 2)
        except Exception:
            continue

    buf = feed_text()
    lines, gaps = [], []
    for tk, ch in sorted(moves.items(), key=lambda x: -abs(x[1])):
        pat = re.compile(r"(?<![A-Za-z])\$?" + tk + r"(?![A-Za-z])")
        inflow = [r for txt, r in buf if pat.search(txt) or WATCH[tk] in txt]
        n_all = len(inflow)
        n_rej = sum(1 for r in inflow if r)
        if n_all == 0:
            st = "미포착"; gaps.append({"tk": tk, "chg": ch, "reason": "수집안됨"})
        elif n_all == n_rej:
            st = f"수집됐으나 전량 컷({n_rej}건)"
            gaps.append({"tk": tk, "chg": ch, "reason": "필터오판"})
        else:
            st = f"포착 {n_all - n_rej}건"
        lines.append(f"{tk} {ch:+.2f}% · {st}")

    hdr = f"[그물점검 {datetime.date.today()}] 5% 이상 {len(moves)}종목"
    msg = hdr + "\n" + ("\n".join(lines) if lines else "해당 없음")
    if gaps:
        msg += "\n\n구멍 " + str(len(gaps)) + "건 — 룰 수정 논의 필요"
        with open(GAPLOG, "a", encoding="utf-8") as f:
            for g in gaps:
                g["date"] = str(datetime.date.today())
                f.write(json.dumps(g, ensure_ascii=False) + "\n")
    print(msg)
    if not DRY: send(msg)

if __name__ == "__main__":
    main()
