import json, re, subprocess, time, requests
from pathlib import Path
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from logger import get_system_logger, log_exception
import shutil
# --- 配置 ---
SESSION_DIR = Path("./session")
COOKIE_FILE = Path("./cookies.json")
# BILIUP_PATH = Path("./biliup")
BILIUP_PATH = shutil.which("biliup") or "./biliup"
MAX_RETRIES, BASE_DELAY, POLL_INTERVAL = 5, 180, 10
ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")

# 初始化系统日志
logger = get_system_logger("session_top_comment")

def strip_ansi(text: str) -> str:
    return ANSI_ESCAPE.sub("", text or "")

class TopCommentClient:
    def __init__(self):
        with open(COOKIE_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
        ck = {c["name"]: c["value"] for c in data.get("cookie_info", {}).get("cookies", [])}
        self.csrf = ck.get("bili_jct")
        if not self.csrf: raise ValueError("Cookie 中缺少 bili_jct")
        self.session = requests.Session()
        self.session.cookies.update(ck)
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            "Referer": "https://www.bilibili.com/",
            "Origin": "https://www.bilibili.com"
        })

    def get_aid(self, bvid):
        res = self.session.get("https://api.bilibili.com/x/web-interface/view", params={"bvid": bvid}).json()
        if res.get('code') != 0: raise RuntimeError(f"View API: {res.get('message')}")
        return res['data']['aid']

    def post_and_top(self, aid, msg):
        # 1. 发表评论
        r = self.session.post("https://api.bilibili.com/x/v2/reply/add", 
                             data={"type": 1, "oid": aid, "message": msg, "plat": 1, "csrf": self.csrf}).json()
        if r.get('code') != 0: raise RuntimeError(f"Post API: {r.get('message')}")
        rpid = r['data']['rpid']
        
        # 2. 等待 3s 数据库同步
        logger.info(f"评论已发布(rpid={rpid})，等待 3s 置顶...")
        time.sleep(3)
        
        # 3. 置顶
        r = self.session.post("https://api.bilibili.com/x/v2/reply/top", 
                             data={"type": 1, "oid": aid, "rpid": rpid, "action": 1, "csrf": self.csrf}).json()
        if r.get('code') != 0: raise RuntimeError(f"Top API: {r.get('message')}")

class CommentManager:
    def __init__(self):
        self.client = TopCommentClient()
        self.pending = {} # {folder_path: {'attempts': 0, 'next_run': 0}}
        self._cache = {"time": 0, "videos": []}

    def fetch_videos(self):
        """调用 biliup list 获取最近上传视频"""
        if time.time() - self._cache["time"] < 60: return self._cache["videos"]
        try:
            res = subprocess.run([str(BILIUP_PATH), "list", "--max-pages", "1"], capture_output=True, text=True, encoding='utf-8')
            videos = []
            for line in strip_ansi(res.stdout).splitlines():
                if line.startswith("BV"):
                    parts = line.split("\t")
                    if len(parts) >= 2: videos.append({"bvid": parts[0].strip(), "title": parts[1].strip()})
            self._cache = {"time": time.time(), "videos": videos}
            return videos
        except Exception as e:
            logger.error(f"biliup list 失败: {e}")
            return []

    def scan_and_add(self, folder: Path):
        if not folder.is_dir() or (folder / "comment_done.flag").exists(): return
        if (folder / "songs.txt").exists() and folder not in self.pending:
            logger.info(f"发现待处理任务: {folder.name}")
            self.pending[folder] = {'attempts': 0, 'next_run': 0}

    def process_queue(self):
        now = time.time()
        for folder in list(self.pending.keys()):
            task = self.pending[folder]
            if task['next_run'] > now: continue
            
            try:
                # 1. 查找匹配视频
                videos = self.fetch_videos()
                # 模糊匹配：文件夹名包含在视频标题中，或视频标题包含在文件夹名中
                matched_bvid = next((v['bvid'] for v in videos if folder.name in v['title'] or v['title'] in folder.name), None)
                
                # 如果没找到，也尝试从文件夹名提取 [BV...]
                if not matched_bvid:
                    bv_match = re.search(r"\[(BV[0-9A-Za-z]+)\]", folder.name)
                    if bv_match: matched_bvid = bv_match.group(1)

                if not matched_bvid:
                    raise RuntimeError("未在最近上传列表中找到匹配视频")

                # 2. 读取内容
                content = (folder / "songs.txt").read_text(encoding="utf-8").strip()
                if not content:
                    logger.warning(f"songs.txt 内容为空，取消任务: {folder.name}")
                    self.pending.pop(folder); continue

                # 3. 执行发布和置顶
                aid = self.client.get_aid(matched_bvid)
                self.client.post_and_top(aid, content)
                
                # 4. 成功标记
                (folder / "comment_done.flag").touch()
                logger.info(f"任务完成: {folder.name} -> {matched_bvid}")
                self.pending.pop(folder)
                
            except Exception as e:
                task['attempts'] += 1
                if task['attempts'] >= MAX_RETRIES:
                    logger.error(f"任务最终失败: {folder.name} - {e}")
                    self.pending.pop(folder)
                else:
                    delay = BASE_DELAY * (2 ** (task['attempts']-1))
                    task['next_run'] = now + delay
                    logger.warning(f"任务推迟({task['attempts']}/{MAX_RETRIES}): {folder.name} - {e}. {delay}s 后重试")

def main():
    logger.info("="*50)
    logger.info("置顶评论模块启动")
    logger.info("="*50)
    
    try:
        mgr = CommentManager()
    except Exception as e:
        logger.error(f"初始化失败: {e}")
        return

    # 1. 初始扫描
    for f in SESSION_DIR.iterdir(): mgr.scan_and_add(f)
    
    # 2. 启动 Watchdog
    class Handler(FileSystemEventHandler):
        def on_created(self, event):
            p = Path(event.src_path)
            if p.name == "songs.txt": mgr.scan_and_add(p.parent)
            
    observer = Observer()
    observer.schedule(Handler(), str(SESSION_DIR), recursive=True)
    observer.start()
    
    logger.info(f"开始监控目录: {SESSION_DIR}")
    try:
        while True:
            mgr.process_queue()
            time.sleep(POLL_INTERVAL)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
    logger.info("置顶评论模块已停止")

if __name__ == "__main__":
    main()
