#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Verify Checkpoint (hỗ trợ tay) — AI Maketing Facebook

Luồng:
  1. Mở Chromium HIỆN (không headless) + proxy nick (nếu có)
  2. Nạp cookie đã lưu khi dính checkpoint
  3. Tự bấm Continue / nhập 2FA / mật khẩu khi được
  4. Nếu cần CAPTCHA / video / human → giữ browser + hiện hộp thoại
     để BẠN làm trên màn hình, rồi bấm "ĐÃ XONG"
  5. LIVE → lưu cookie mới, đánh dấu LIVE

Chạy độc lập:
  python3 verify_checkpoint_assist.py

Hoặc bấm nút trong app: "🛡 Verify Checkpoint (cookie + hỗ trợ tay)"
"""

from __future__ import annotations

import asyncio
import base64
import hashlib
import hmac
import json
import os
import re
import struct
import threading
import time
from datetime import datetime
from pathlib import Path

import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext

DATA_FILE = "accounts_v9.json"
OUT_DIR = "checkpoint_verify"
REPORT_NAME = "assist_report.json"

# ---------------------------------------------------------------------------
# TOTP
# ---------------------------------------------------------------------------
def generate_totp(secret: str) -> str:
    secret = re.sub(r"\s+", "", (secret or "").upper())
    if not secret:
        return ""
    try:
        import pyotp

        return pyotp.TOTP(secret).now()
    except Exception:
        pass
    try:
        pad = "=" * ((8 - len(secret) % 8) % 8)
        key = base64.b32decode(secret + pad, casefold=True)
        counter = struct.pack(">Q", int(time.time()) // 30)
        hs = hmac.new(key, counter, hashlib.sha1).digest()
        o = hs[-1] & 0x0F
        code = (
            (hs[o] & 0x7F) << 24
            | (hs[o + 1] & 0xFF) << 16
            | (hs[o + 2] & 0xFF) << 8
            | (hs[o + 3] & 0xFF)
        )
        return f"{code % 1_000_000:06d}"
    except Exception:
        return ""


def cookie_header_to_list(cookie_str: str):
    out = []
    for item in (cookie_str or "").split(";"):
        item = item.strip()
        if "=" in item:
            k, v = item.split("=", 1)
            out.append(
                {
                    "name": k.strip(),
                    "value": v.strip(),
                    "domain": ".facebook.com",
                    "path": "/",
                }
            )
    return out


def export_cookies_sync(cookies):
    seen = {}
    for c in cookies:
        if "facebook.com" in (c.get("domain") or ""):
            seen[c["name"]] = c["value"]
    return ";".join(f"{k}={v}" for k, v in seen.items())


def build_proxy(proxy_str: str):
    proxy_str = (proxy_str or "").strip()
    if not proxy_str:
        return None
    parts = proxy_str.split(":")
    if len(parts) == 4:
        return {
            "server": f"http://{parts[0]}:{parts[1]}",
            "username": parts[2],
            "password": parts[3],
        }
    if len(parts) == 2:
        return {"server": f"http://{parts[0]}:{parts[1]}"}
    return None


# ---------------------------------------------------------------------------
# Page helpers
# ---------------------------------------------------------------------------
async def page_status(page) -> str:
    """LIVE | CHECKPOINT | DIE | UNKNOWN"""
    try:
        url = (page.url or "").lower()
        content = (await page.content()).lower()
        cl = content[:15000]
        cookies = await page.context.cookies()
        has_user = any(c.get("name") == "c_user" for c in cookies)

        if any(
            x in url
            for x in (
                "checkpoint",
                "two_step",
                "security_checkup",
                "disabled",
                "confirmemail",
                "two_factor",
                "approvals",
            )
        ):
            return "CHECKPOINT"
        signals = (
            "confirm you are human",
            "xác nhận bạn là người",
            "identity confirm",
            "confirm your identity",
            "we suspended",
            "account has been disabled",
            "upload a photo of your id",
            "enter the code",
            "security check",
            "captcha",
            "recaptcha",
        )
        if any(s in url or s in cl for s in signals):
            return "CHECKPOINT"
        if "login" in url and ("name=\"pass\"" in cl or "name='pass'" in cl) and not has_user:
            return "DIE"
        if has_user and "login" not in url:
            return "LIVE"
        if has_user and "checkpoint" not in url:
            # cookie còn, có thể feed
            try:
                feed = await page.query_selector('[aria-label="Facebook"], [role="navigation"]')
                if feed:
                    return "LIVE"
            except Exception:
                pass
            if "home" in url or "facebook.com/" == url.rstrip("/").split("?")[0][-14:]:
                return "LIVE"
        if not has_user:
            return "DIE"
        return "UNKNOWN"
    except Exception:
        return "UNKNOWN"


VIDEO_SIGNALS = (
    "record a video",
    "take a selfie",
    "upload a photo of your id",
    "video selfie",
    "quay video",
    "video selfie confirmation",
    "confirm your identity with a video",
    "move your head",
    "look at the camera",
    "nhìn vào camera",
    "quay một video",
    "xác nhận bằng video",
    "upload an id",
    "government id",
    "id verification",
    "submit a video",
    "record yourself",
    "face confirmation",
    "xác minh khuôn mặt",
    "selfie video",
    "camera permission",
)


async def page_requires_video(page) -> bool:
    """True nếu FB bắt quay video / selfie / upload CMND — NÊN BỎ NICK."""
    try:
        url = (page.url or "").lower()
        content = (await page.content()).lower()
        c = content[:25000]
    except Exception:
        return False
    if any(s in url or s in c for s in VIDEO_SIGNALS):
        return True
    # URL checkpoint types often used for identity/video
    if "checkpoint" in url and any(
        x in url or x in c for x in ("identity", "selfie", "video", "id_upload", "photo_id")
    ):
        return True
    return False


async def page_has_captcha_or_human(page) -> tuple:
    """
    True nếu trang cần user tự làm (captcha / human — KHÔNG gồm video).
    Video tách riêng → SKIP + báo cáo bỏ nick.
    Trả (need_stop: bool, reason: str).
    """
    try:
        url = (page.url or "").lower()
        content = (await page.content()).lower()
        c = content[:20000]
    except Exception:
        return False, ""

    # Video → caller xử lý SKIP (không chờ user)
    if await page_requires_video(page):
        return True, "VIDEO_SKIP"

    # Captcha rõ ràng
    captcha_signals = (
        "captcha",
        "recaptcha",
        "g-recaptcha",
        "hcaptcha",
        "cf-turnstile",
        "arkose",
        "funcaptcha",
        "challenge-form",
        "security check",
        "i'm not a robot",
        "không phải robot",
        "xác minh bạn không phải",
    )
    if any(s in url or s in c for s in captcha_signals):
        return True, "CAPTCHA — DỪNG. Bạn tự giải captcha trên browser, xong bấm ĐÃ XONG"

    # Human confirm (không phải video)
    human_signals = (
        "confirm you are human",
        "xác nhận bạn là người",
        "confirm that you're human",
        "we need to confirm",
    )
    if any(s in c for s in human_signals) and not await page_requires_video(page):
        return True, "CONFIRM HUMAN — DỪNG. Bấm Continue / captcha trên browser (nếu có)"

    # Checkpoint URL (chờ user captcha — trừ video đã lọc)
    if "checkpoint" in url:
        return True, "CHECKPOINT — DỪNG. Bạn tự xử lý captcha/Continue trên browser"

    # iframe captcha
    try:
        for fr in page.frames:
            fu = (fr.url or "").lower()
            if any(x in fu for x in ("recaptcha", "hcaptcha", "captcha", "arkose", "funcaptcha")):
                return True, "CAPTCHA (iframe) — DỪNG. Bạn tự giải trên browser"
    except Exception:
        pass

    return False, ""


async def try_fill_2fa_only(page, secret: str, log) -> bool:
    """Chỉ điền mã 2FA khi thấy ô approvals_code — KHÔNG bấm Continue lung tung."""
    if await page_has_captcha_or_human(page)[0]:
        log("  → Có captcha/human — KHÔNG auto 2FA, chờ bạn")
        return False
    code = generate_totp(secret)
    if not code:
        return False
    # Chỉ selector 2FA rõ ràng (không đụng input text thường = tránh captcha)
    selectors = (
        'input[name="approvals_code"]',
        "input#approvals_code",
        'input[autocomplete="one-time-code"]',
    )
    for sel in selectors:
        try:
            el = await page.query_selector(sel)
            if not el or not await el.is_visible():
                continue
            log(f"  → Điền 2FA: {code}")
            await el.fill("")
            await el.fill(code)
            await asyncio.sleep(0.3)
            # Chỉ bấm submit cạnh form 2FA
            for btn_sel in (
                'button[name="submit"]',
                'button[type="submit"]',
                'button#checkpointSubmitButton',
            ):
                try:
                    btn = await page.query_selector(btn_sel)
                    if btn and await btn.is_visible():
                        await btn.click(timeout=2000)
                        break
                except Exception:
                    continue
            await asyncio.sleep(2.5)
            return True
        except Exception:
            continue
    return False


async def try_password_login_once(page, acc, log) -> str:
    """
    Chỉ khi đang form login (DIE). Điền email+pass 1 lần.
    Sau login nếu captcha → dừng ngay, không auto tiếp.
    Trả LIVE | NEED_HUMAN | DIE | CHECKPOINT | UNKNOWN
    """
    password = acc.get("password") or ""
    login_id = acc.get("email") or acc.get("uid") or ""
    if "Device=" in str(login_id) or len(str(login_id)) > 80:
        login_id = acc.get("uid") or ""
    if not password or not login_id:
        log("  → Thiếu mật khẩu/UID")
        return "DIE"
    try:
        # đã captcha? đừng đụng
        stop, reason = await page_has_captcha_or_human(page)
        if stop:
            log(f"  → {reason}")
            return "NEED_HUMAN"

        email_el = await page.query_selector('input[name="email"], input#email')
        pass_el = await page.query_selector('input[name="pass"], input#pass')
        if not email_el or not pass_el:
            # thử mở login
            await page.goto(
                "https://www.facebook.com/login",
                timeout=60000,
                wait_until="domcontentloaded",
            )
            await asyncio.sleep(2)
            stop, reason = await page_has_captcha_or_human(page)
            if stop:
                log(f"  → {reason}")
                return "NEED_HUMAN"
            email_el = await page.query_selector('input[name="email"], input#email')
            pass_el = await page.query_selector('input[name="pass"], input#pass')
        if not email_el or not pass_el:
            log("  → Không thấy form login")
            return await page_status(page)

        log(f"  → Điền login: {login_id} (1 lần, không auto captcha)")
        await email_el.fill(str(login_id))
        await pass_el.fill(password)
        await page.click('button[name="login"], button[type="submit"]')
        await asyncio.sleep(4)

        if await page_requires_video(page):
            log("  → Sau login: VIDEO → NÊN BỎ NICK")
            return "SKIP_VIDEO"

        stop, reason = await page_has_captcha_or_human(page)
        if stop:
            if reason == "VIDEO_SKIP" or await page_requires_video(page):
                return "SKIP_VIDEO"
            log(f"  → Sau login: {reason}")
            return "NEED_HUMAN"

        url = (page.url or "").lower()
        if "two_factor" in url or "approvals" in url or "two-factor" in url:
            await try_fill_2fa_only(page, acc.get("two_fa") or "", log)
            await asyncio.sleep(2)
            if await page_requires_video(page):
                return "SKIP_VIDEO"
            stop, reason = await page_has_captcha_or_human(page)
            if stop:
                if reason == "VIDEO_SKIP":
                    return "SKIP_VIDEO"
                log(f"  → Sau 2FA: {reason}")
                return "NEED_HUMAN"

        st = await page_status(page)
        if await page_requires_video(page):
            return "SKIP_VIDEO"
        if st == "CHECKPOINT":
            return "NEED_HUMAN"
        return st
    except Exception as ex:
        log(f"  → Login lỗi: {ex}")
        return "UNKNOWN"


async def prepare_and_open(page, acc, log) -> str:
    """
    Nạp cookie → mở FB.
    VIDEO → SKIP_VIDEO (bỏ nick, không chờ).
    Captcha/human → NEED_HUMAN (chờ user).
    DIE → điền pass 1 lần.
    """
    uid = acc.get("uid", "")

    if acc.get("cookie"):
        try:
            await page.context.clear_cookies()
            await page.context.add_cookies(cookie_header_to_list(acc["cookie"]))
            log(f"[{uid}] Đã nạp cookie")
        except Exception as ex:
            log(f"[{uid}] Lỗi nạp cookie: {ex}")
    else:
        log(f"[{uid}] Không có cookie — thử form login nếu cần")

    try:
        await page.goto(
            "https://www.facebook.com/",
            timeout=60000,
            wait_until="domcontentloaded",
        )
        await asyncio.sleep(3)
    except Exception as ex:
        log(f"[{uid}] Không mở được FB: {ex}")
        return "UNKNOWN"

    # VIDEO trước — bỏ nick ngay
    if await page_requires_video(page):
        log(f"[{uid}] ⛔ FB bắt quay VIDEO/selfie/CMND → BỎ QUA, NÊN BỎ NICK")
        log(f"[{uid}] URL: {page.url[:140]}")
        return "SKIP_VIDEO"

    stop, reason = await page_has_captcha_or_human(page)
    if stop:
        if reason == "VIDEO_SKIP" or await page_requires_video(page):
            log(f"[{uid}] ⛔ VIDEO → BỎ QUA, NÊN BỎ NICK")
            return "SKIP_VIDEO"
        log(f"[{uid}] 🛑 {reason}")
        log(f"[{uid}] URL: {page.url[:120]}")
        return "NEED_HUMAN"

    st = await page_status(page)
    log(f"[{uid}] Sau cookie → {st} | {page.url[:90]}")
    if st == "LIVE":
        return "LIVE"

    if st == "CHECKPOINT":
        if await page_requires_video(page):
            return "SKIP_VIDEO"
        log(f"[{uid}] 🛑 Checkpoint (không video) — chờ captcha tay")
        return "NEED_HUMAN"

    if st == "DIE":
        log(f"[{uid}] Cookie DIE — điền mật khẩu 1 lần")
        st2 = await try_password_login_once(page, acc, log)
        if await page_requires_video(page) or st2 == "SKIP_VIDEO":
            return "SKIP_VIDEO"
        if st2 in ("NEED_HUMAN", "CHECKPOINT"):
            if await page_requires_video(page):
                return "SKIP_VIDEO"
            return "NEED_HUMAN"
        return st2

    log(f"[{uid}] 🛑 Không rõ — chờ bạn kiểm tra (captcha)")
    return "NEED_HUMAN"


def detect_need_human(url: str, content_snippet: str) -> str:
    u = (url or "").lower()
    c = (content_snippet or "").lower()
    if any(s in u or s in c for s in VIDEO_SIGNALS):
        return "VIDEO — NÊN BỎ NICK (tool đã skip)"
    if "captcha" in u or "captcha" in c or "recaptcha" in c or "g-recaptcha" in c:
        return "CAPTCHA — bạn TỰ giải trên browser, tool không tự điền"
    if "confirm you are human" in c or "xác nhận bạn là người" in c:
        return "CONFIRM HUMAN — bạn bấm Continue / captcha trên browser"
    if "checkpoint" in u:
        return "CHECKPOINT — bạn tự làm captcha/Continue trên browser"
    if "two_factor" in u or "approvals" in u:
        return "2FA — nhập mã nếu cần; captcha vẫn do bạn"
    return "CẦN BẠN xử lý captcha trên browser — tool đã DỪNG"


def mark_discard_video(acc, url="") -> dict:
    """Đánh dấu nick nên bỏ vì checkpoint video."""
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    note = "NÊN BỎ NICK — FB bắt quay video/selfie/CMND (không verify được bằng tool)"
    acc["status"] = "CHECKPOINT"
    acc["login_status"] = "BỎ: checkpoint VIDEO"
    acc["scenario_note"] = note
    acc["last_check"] = now
    acc["discard_reason"] = "video_selfie_required"
    acc["discard_recommend"] = True
    return {
        "uid": str(acc.get("uid") or ""),
        "email": acc.get("email") or "",
        "proxy": acc.get("proxy_fingerprint") or acc.get("proxy") or "",
        "time": now,
        "result": "SKIP_VIDEO",
        "recommend": "NÊN BỎ NICK",
        "message": note,
        "url": (url or "")[:240],
    }


# ---------------------------------------------------------------------------
# UI controller
# ---------------------------------------------------------------------------
class VerifyAssistApp:
    def __init__(self, root, base_dir=None, accounts=None, on_saved=None):
        self.root = root
        self.base_dir = Path(base_dir or os.path.dirname(os.path.abspath(__file__)))
        self.on_saved = on_saved  # callback(accounts) when saved
        self.accounts = accounts if accounts is not None else self._load_accounts()
        self.cp_indices = [
            i
            for i, a in enumerate(self.accounts)
            if "CHECKPOINT" in str(a.get("status") or "").upper()
            or "checkpoint" in str(a.get("login_status") or "").lower()
        ]
        self._stop = threading.Event()
        self._user_done = threading.Event()
        self._user_skip = threading.Event()
        self._running = False
        self._build_ui()

    def _load_accounts(self):
        path = self.base_dir / DATA_FILE
        if not path.exists():
            return []
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)

    def _save_accounts(self):
        path = self.base_dir / DATA_FILE
        with open(path, "w", encoding="utf-8") as f:
            json.dump(self.accounts, f, indent=4, ensure_ascii=False)
        if self.on_saved:
            try:
                self.on_saved(self.accounts)
            except Exception:
                pass

    def _build_ui(self):
        self.root.title("Verify Checkpoint — hỗ trợ tay (cookie + 2FA)")
        self.root.geometry("820x640")
        self.root.configure(bg="#e2e8f0")

        head = tk.Frame(self.root, bg="#312e81")
        head.pack(fill="x")
        tk.Label(
            head,
            text="🛡 Verify Checkpoint (cookie đã lưu + hỗ trợ captcha)",
            font=("Helvetica", 14, "bold"),
            bg="#312e81",
            fg="#ffffff",
        ).pack(anchor="w", padx=16, pady=(12, 2))
        tk.Label(
            head,
            text="Captcha: bạn tự điền. Gặp VIDEO/selfie → BỎ QUA nick + ghi báo cáo NÊN BỎ NICK.",
            font=("Helvetica", 10),
            bg="#312e81",
            fg="#c7d2fe",
        ).pack(anchor="w", padx=16, pady=(0, 12))

        info = tk.Frame(self.root, bg="#e2e8f0")
        info.pack(fill="x", padx=14, pady=8)
        self.lbl_info = tk.Label(
            info,
            text=f"Tìm thấy {len(self.cp_indices)} nick CHECKPOINT / tổng {len(self.accounts)}",
            font=("Helvetica", 11, "bold"),
            bg="#e2e8f0",
            fg="#0f172a",
        )
        self.lbl_info.pack(anchor="w")

        self.lbl_status = tk.Label(
            info,
            text="Sẵn sàng. Bấm BẮT ĐẦU VERIFY.",
            font=("Helvetica", 11),
            bg="#fef3c7",
            fg="#92400e",
            wraplength=780,
            justify="left",
            padx=10,
            pady=8,
        )
        self.lbl_status.pack(fill="x", pady=(8, 0))

        self.lbl_need = tk.Label(
            info,
            text="",
            font=("Helvetica", 13, "bold"),
            bg="#fee2e2",
            fg="#991b1b",
            wraplength=780,
            justify="left",
            padx=10,
            pady=10,
        )
        self.lbl_need.pack(fill="x", pady=(8, 0))

        btns = tk.Frame(self.root, bg="#e2e8f0")
        btns.pack(fill="x", padx=14, pady=6)
        # Canvas buttons — macOS tk.Button hay chữ trắng xoá
        try:
            from ui_contrast import solid_button
        except ImportError:
            from pathlib import Path
            import sys
            sys.path.insert(0, str(Path(__file__).resolve().parent))
            from ui_contrast import solid_button

        self.btn_start = solid_button(
            btns, "▶  BẮT ĐẦU VERIFY (từng nick)", command=self.start,
            bg="#15803d", fg="#ffffff", hover="#166534", border="#14532d", min_width=220,
        )
        self.btn_start.pack(side="left", padx=4)
        self.btn_done = solid_button(
            btns, "✓  ĐÃ XONG (đã captcha / human)", command=self.mark_done,
            bg="#1d4ed8", fg="#ffffff", hover="#1e40af", border="#1e3a8a",
            min_width=220, state="disabled",
        )
        self.btn_done.pack(side="left", padx=4)
        self.btn_skip = solid_button(
            btns, "⏭  Bỏ qua nick này", command=self.mark_skip,
            bg="#475569", fg="#ffffff", hover="#334155", border="#1e293b",
            min_width=150, state="disabled",
        )
        self.btn_skip.pack(side="left", padx=4)
        self.btn_stop = solid_button(
            btns, "⏹  Dừng", command=self.stop,
            bg="#b91c1c", fg="#ffffff", hover="#991b1b", border="#7f1d1d", min_width=100,
        )
        self.btn_stop.pack(side="left", padx=4)

        tk.Label(
            self.root,
            text="Log:",
            bg="#e2e8f0",
            fg="#0f172a",
            font=("Helvetica", 10, "bold"),
        ).pack(anchor="w", padx=14)
        self.log_box = scrolledtext.ScrolledText(
            self.root,
            height=18,
            font=("Menlo", 10),
            bg="#0f172a",
            fg="#bbf7d0",
            insertbackground="#fff",
        )
        self.log_box.pack(fill="both", expand=True, padx=14, pady=(4, 14))

        self._list_accounts()

    def _list_accounts(self):
        self.log(f"=== {len(self.cp_indices)} nick checkpoint ===")
        for n, i in enumerate(self.cp_indices, 1):
            a = self.accounts[i]
            px = a.get("proxy_fingerprint") or a.get("proxy") or "(không proxy)"
            if isinstance(px, str) and len(px) > 40:
                px = px[:40] + "…"
            self.log(
                f"  {n}. UID {a.get('uid')} | email={a.get('email','')} | "
                f"2FA={'có' if a.get('two_fa') else 'không'} | proxy={px}"
            )

    def log(self, msg: str):
        line = f"[{datetime.now().strftime('%H:%M:%S')}] {msg}"

        def _do():
            self.log_box.insert("end", line + "\n")
            self.log_box.see("end")

        try:
            self.root.after(0, _do)
        except Exception:
            print(line)

    def set_status(self, text, urgent=False):
        def _do():
            self.lbl_status.config(
                text=text,
                bg="#fee2e2" if urgent else "#fef3c7",
                fg="#991b1b" if urgent else "#92400e",
            )

        self.root.after(0, _do)

    def set_need(self, text):
        def _do():
            self.lbl_need.config(text=text or "")
            if text:
                try:
                    self.btn_done.set_state("normal")
                    self.btn_skip.set_state("normal")
                except Exception:
                    self.btn_done.config(state="normal")
                    self.btn_skip.config(state="normal")
            else:
                try:
                    self.btn_done.set_state("disabled")
                    self.btn_skip.set_state("disabled")
                except Exception:
                    self.btn_done.config(state="disabled")
                    self.btn_skip.config(state="disabled")

        self.root.after(0, _do)

    def start(self):
        if self._running:
            return
        if not self.cp_indices:
            messagebox.showinfo("Verify", "Không còn nick CHECKPOINT.")
            return
        self._stop.clear()
        self._running = True
        try:
            self.btn_start.set_state("disabled")
        except Exception:
            self.btn_start.config(state="disabled")
        threading.Thread(target=self._thread_main, daemon=True).start()

    def stop(self):
        self._stop.set()
        self._user_done.set()
        self._user_skip.set()
        self.set_status("Đang dừng…")
        self.log("⏹ Yêu cầu dừng")

    def mark_done(self):
        self.log("✓ Bạn bấm ĐÃ XONG — đang kiểm tra lại session…")
        self._user_done.set()

    def mark_skip(self):
        self.log("⏭ Bỏ qua nick này")
        self._user_skip.set()
        self._user_done.set()

    def _thread_main(self):
        try:
            asyncio.run(self._run_all())
        except Exception as ex:
            self.log(f"LỖI: {ex}")
        finally:
            self._running = False

            def _re():
                try:
                    self.btn_start.set_state("normal")
                except Exception:
                    try:
                        self.btn_start.config(state="normal")
                    except Exception:
                        pass

            self.root.after(0, _re)
            self.set_need("")
            self.set_status("Xong / dừng.")

    async def _run_all(self):
        try:
            from playwright.async_api import async_playwright
        except ImportError:
            self.log("Chưa cài playwright: pip install playwright && playwright install chromium")
            return

        out_dir = self.base_dir / OUT_DIR
        out_dir.mkdir(exist_ok=True)
        report = []
        discard_list = []  # nick NÊN BỎ (video)
        ok = fail = skip = discard = 0

        async with async_playwright() as p:
            for n, idx in enumerate(list(self.cp_indices), 1):
                if self._stop.is_set():
                    break
                acc = self.accounts[idx]
                uid = str(acc.get("uid") or "")
                self.set_status(f"[{n}/{len(self.cp_indices)}] Đang xử lý UID {uid}…")
                self.set_need("")
                self.log("=" * 60)
                self.log(f"[{n}/{len(self.cp_indices)}] UID {uid} | {acc.get('email','')}")

                proxy = build_proxy(acc.get("proxy") or "")
                if proxy:
                    self.log(f"  Proxy: {acc.get('proxy_fingerprint') or acc.get('proxy')}")
                else:
                    self.log("  ⚠ Không có proxy — dùng IP máy (rủi ro).")

                launch_args = [
                    "--disable-blink-features=AutomationControlled",
                    "--no-sandbox",
                    "--disable-dev-shm-usage",
                    "--start-maximized",
                ]
                try:
                    browser = await p.chromium.launch(
                        headless=False, channel="chrome", args=launch_args
                    )
                except Exception:
                    browser = await p.chromium.launch(headless=False, args=launch_args)
                context = await browser.new_context(
                    proxy=proxy,
                    user_agent=(
                        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                        "AppleWebKit/537.36 (KHTML, like Gecko) "
                        "Chrome/122.0.0.0 Safari/537.36"
                    ),
                    locale="vi-VN",
                    viewport={"width": 1280, "height": 860},
                )
                page = await context.new_page()
                shot = out_dir / f"assist_{n}_{uid}.png"
                result = {
                    "uid": uid,
                    "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "result": "UNKNOWN",
                    "message": "",
                    "recommend": "",
                }
                try:
                    st = await prepare_and_open(page, acc, self.log)
                    # Kiểm tra video thêm lần nữa (sau login)
                    if st not in ("LIVE", "SKIP_VIDEO") and await page_requires_video(page):
                        st = "SKIP_VIDEO"
                    try:
                        await page.screenshot(path=str(shot), full_page=False)
                    except Exception:
                        pass

                    if st == "SKIP_VIDEO":
                        # Bỏ qua ngay + báo cáo NÊN BỎ NICK
                        row = mark_discard_video(acc, url=page.url or "")
                        self._save_accounts()
                        discard += 1
                        discard_list.append(row)
                        result.update(row)
                        self.log(f"[{uid}] ⛔ SKIP VIDEO — NÊN BỎ NICK (đã ghi báo cáo)")
                        self.set_status(f"⛔ {uid} — VIDEO → NÊN BỎ NICK (bỏ qua)")
                        await asyncio.sleep(0.8)

                    elif st == "LIVE":
                        cookie = export_cookies_sync(await context.cookies())
                        if cookie:
                            acc["cookie"] = cookie
                        acc["status"] = "LIVE"
                        acc["login_status"] = "Cookie LIVE (verify)"
                        acc["scenario_note"] = ""
                        acc["discard_recommend"] = False
                        acc["last_check"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        self._save_accounts()
                        ok += 1
                        result["result"] = "LIVE"
                        result["message"] = "LIVE — đã lưu cookie"
                        self.log(f"[{uid}] ✅ LIVE — đã lưu cookie")
                        self.set_status(f"✅ {uid} LIVE")

                    else:
                        # NEED_HUMAN: captcha (không video) — chờ user
                        # Nếu giữa chừng phát hiện video → skip discard
                        try:
                            body = await page.content()
                        except Exception:
                            body = ""
                        if await page_requires_video(page) or any(
                            s in body.lower() for s in VIDEO_SIGNALS
                        ):
                            row = mark_discard_video(acc, url=page.url or "")
                            self._save_accounts()
                            discard += 1
                            discard_list.append(row)
                            result.update(row)
                            self.log(f"[{uid}] ⛔ Phát hiện VIDEO khi chờ → NÊN BỎ NICK")
                            self.set_status(f"⛔ {uid} VIDEO → bỏ nick")
                        else:
                            need = detect_need_human(page.url, body[:8000])
                            totp = generate_totp(acc.get("two_fa") or "")
                            hint = (
                                f"🛑 CAPTCHA — UID {uid} (bạn tự điền)\n"
                                f"{need}\n"
                                f"Email: {acc.get('email','')}  |  Pass: {acc.get('password','')}\n"
                                f"2FA: {totp or acc.get('two_fa') or '—'}\n"
                                f"→ Xong bấm «ĐÃ XONG»  |  Nếu ra VIDEO tool sẽ bỏ nick"
                            )
                            self.set_need(hint)
                            self.set_status(
                                f"🛑 Captcha — UID {uid}. Bạn tự điền → ĐÃ XONG",
                                urgent=True,
                            )
                            self.log(f"[{uid}] 🛑 Chờ captcha tay")

                            self._user_done.clear()
                            self._user_skip.clear()
                            deadline = time.time() + 15 * 60
                            final = "CHECKPOINT"
                            while time.time() < deadline and not self._stop.is_set():
                                if self._user_skip.is_set():
                                    final = "SKIP"
                                    break
                                # Giữa chừng ra video → bỏ nick
                                if await page_requires_video(page):
                                    final = "SKIP_VIDEO"
                                    break
                                try:
                                    cur = await page_status(page)
                                except Exception:
                                    cur = "UNKNOWN"
                                if cur == "LIVE":
                                    final = "LIVE"
                                    break
                                if self._user_done.is_set():
                                    self._user_done.clear()
                                    if await page_requires_video(page):
                                        final = "SKIP_VIDEO"
                                        break
                                    try:
                                        if "checkpoint" not in (page.url or "").lower():
                                            await page.goto(
                                                "https://www.facebook.com/",
                                                timeout=45000,
                                                wait_until="domcontentloaded",
                                            )
                                            await asyncio.sleep(2)
                                    except Exception:
                                        pass
                                    if await page_requires_video(page):
                                        final = "SKIP_VIDEO"
                                        break
                                    final = await page_status(page)
                                    self.log(f"[{uid}] Sau ĐÃ XONG → {final}")
                                    if final == "LIVE":
                                        break
                                    self.set_need(
                                        f"Chưa LIVE ({final}). Captcha tiếp trên browser → ĐÃ XONG.\n"
                                        f"2FA: {generate_totp(acc.get('two_fa') or '') or '—'}"
                                    )
                                await asyncio.sleep(2.5)

                            if final == "SKIP_VIDEO":
                                row = mark_discard_video(acc, url=page.url or "")
                                self._save_accounts()
                                discard += 1
                                discard_list.append(row)
                                result.update(row)
                                self.log(f"[{uid}] ⛔ VIDEO → NÊN BỎ NICK")
                            elif final == "LIVE":
                                cookie = export_cookies_sync(await context.cookies())
                                if cookie:
                                    acc["cookie"] = cookie
                                acc["status"] = "LIVE"
                                acc["login_status"] = "Đã verify (user captcha)"
                                acc["scenario_note"] = ""
                                acc["discard_recommend"] = False
                                acc["last_check"] = datetime.now().strftime(
                                    "%Y-%m-%d %H:%M:%S"
                                )
                                self._save_accounts()
                                ok += 1
                                result["result"] = "LIVE"
                                result["message"] = "Captcha OK — đã lưu cookie"
                                self.log(f"[{uid}] ✅ LIVE")
                            elif final == "SKIP":
                                skip += 1
                                result["result"] = "SKIP"
                                result["message"] = "User bỏ qua"
                                self.log(f"[{uid}] ⏭ Bỏ qua")
                            else:
                                fail += 1
                                acc["status"] = "CHECKPOINT"
                                acc["login_status"] = "Checkpoint: chờ captcha"
                                acc["last_check"] = datetime.now().strftime(
                                    "%Y-%m-%d %H:%M:%S"
                                )
                                self._save_accounts()
                                result["result"] = "CHECKPOINT"
                                result["message"] = (page.url or "")[:200]
                                self.log(f"[{uid}] ❌ Vẫn checkpoint")
                except Exception as ex:
                    fail += 1
                    result["result"] = "ERROR"
                    result["message"] = str(ex)
                    self.log(f"[{uid}] Lỗi: {ex}")
                finally:
                    self.set_need("")
                    report.append(result)
                    try:
                        await browser.close()
                    except Exception:
                        pass
                    await asyncio.sleep(0.6)

        # Báo cáo tổng
        try:
            with open(self.base_dir / OUT_DIR / REPORT_NAME, "w", encoding="utf-8") as f:
                json.dump(report, f, indent=2, ensure_ascii=False)
        except Exception:
            pass

        # File riêng: danh sách NÊN BỎ NICK (video)
        discard_path = self.base_dir / OUT_DIR / "NEN_BO_NICK_VIDEO.json"
        discard_txt = self.base_dir / OUT_DIR / "NEN_BO_NICK_VIDEO.txt"
        try:
            with open(discard_path, "w", encoding="utf-8") as f:
                json.dump(
                    {
                        "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                        "total_discard": len(discard_list),
                        "recommend": "NÊN BỎ các nick này — FB bắt quay video/selfie",
                        "accounts": discard_list,
                    },
                    f,
                    indent=2,
                    ensure_ascii=False,
                )
            lines = [
                f"# NÊN BỎ NICK — checkpoint VIDEO/selfie ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})",
                f"# Tổng: {len(discard_list)} nick",
                "# Lý do: Facebook bắt quay video xác minh — tool bỏ qua, không verify được",
                "",
            ]
            for r in discard_list:
                lines.append(
                    f"{r.get('uid')}\t{r.get('email')}\t{r.get('proxy')}\tNÊN BỎ NICK\t{r.get('message')}"
                )
            discard_txt.write_text("\n".join(lines) + "\n", encoding="utf-8")
            self.log(f"📄 Báo cáo bỏ nick: {discard_path.name} + {discard_txt.name}")
        except Exception as ex:
            self.log(f"Lỗi ghi báo cáo bỏ nick: {ex}")

        msg = (
            f"Xong: LIVE={ok} | NÊN BỎ (video)={discard} | "
            f"FAIL={fail} | SKIP={skip} / {len(self.cp_indices)}"
        )
        self.log(msg)
        if discard_list:
            self.log("--- NÊN BỎ NICK (video) ---")
            for r in discard_list:
                self.log(f"  • {r.get('uid')} | {r.get('email')} | {r.get('recommend')}")
        self.set_status(msg)
        self.cp_indices = [
            i
            for i, a in enumerate(self.accounts)
            if "CHECKPOINT" in str(a.get("status") or "").upper()
            and not a.get("discard_recommend")
        ]
        # vẫn đếm mọi checkpoint còn lại
        self.cp_indices = [
            i
            for i, a in enumerate(self.accounts)
            if "CHECKPOINT" in str(a.get("status") or "").upper()
        ]
        self.root.after(
            0,
            lambda: self.lbl_info.config(
                text=(
                    f"Còn CP: {len(self.cp_indices)} | "
                    f"Nên bỏ (video): {sum(1 for a in self.accounts if a.get('discard_recommend'))}"
                )
            ),
        )
        detail = msg
        if discard_list:
            detail += (
                f"\n\n⛔ {len(discard_list)} nick NÊN BỎ (video):\n"
                + "\n".join(f"• {r.get('uid')}" for r in discard_list[:20])
                + f"\n\nFile: checkpoint_verify/NEN_BO_NICK_VIDEO.txt"
            )
        self.root.after(0, lambda: messagebox.showinfo("Verify Checkpoint", detail))


def run_standalone():
    root = tk.Tk()
    VerifyAssistApp(root)
    root.mainloop()


def run_as_dialog(parent, accounts, on_saved=None, base_dir=None):
    """Mở cửa sổ con từ app chính."""
    win = tk.Toplevel(parent)
    win.transient(parent)
    app = VerifyAssistApp(win, base_dir=base_dir, accounts=accounts, on_saved=on_saved)
    return app


if __name__ == "__main__":
    run_standalone()
