#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Định vị quốc gia nick Facebook + gợi ý nhà cung cấp proxy khu vực.

Mục tiêu: chọn proxy CÙNG quốc gia/khu vực với nick TRƯỚC khi chạy app
→ giảm checkpoint do lệch IP / farm proxy sai vùng.
"""

from __future__ import annotations

import json
import os
import re
import threading
import time
import urllib.request
from collections import Counter
from datetime import datetime

# ---------------------------------------------------------------------------
# Catalog nhà cung cấp proxy theo khu vực (gợi ý mua — link trang chủ)
# ---------------------------------------------------------------------------
PROXY_PROVIDERS = {
    "VN": {
        "name": "Việt Nam",
        "flag": "🇻🇳",
        "timezone": "Asia/Ho_Chi_Minh",
        "locale": "vi_VN",
        "tips": [
            "Ưu tiên proxy dân cư (residential) hoặc mobile xoay VN",
            "1 proxy sạch cho 2–3 nick; không dùng chung IP sau khi 1 nick checkpoint",
            "Tránh datacenter IP công khai — FB dễ human/video check",
        ],
        "providers": [
            {"name": "TMProxy", "type": "VN xoay / static", "url": "https://tmproxy.com", "note": "Phổ biến VN, gói ngày/tháng"},
            {"name": "ProxyXoay", "type": "VN residential", "url": "https://proxyxoay.shop", "note": "Xoay IP Việt"},
            {"name": "WWProxy", "type": "VN proxy", "url": "https://wwproxy.com", "note": "Gói VN"},
            {"name": "KiotProxy", "type": "VN", "url": "https://kiotproxy.com", "note": "Proxy Việt"},
            {"name": "Proxy Viet", "type": "VN", "url": "https://proxyviet.com", "note": "Local VN"},
        ],
    },
    "US": {
        "name": "United States",
        "flag": "🇺🇸",
        "timezone": "America/New_York",
        "locale": "en_US",
        "tips": [
            "Residential US sticky session 10–30 phút cho warm-up",
            "Tránh IP cloud AWS/GCP cho nick mới",
            "Giữ cùng bang/timezone nếu có thể (NY / CA / TX)",
        ],
        "providers": [
            {"name": "IPRoyal", "type": "Residential", "url": "https://iproyal.com", "note": "Giá tốt, sticky"},
            {"name": "Smartproxy", "type": "Residential / Mobile", "url": "https://smartproxy.com", "note": "US city targeting"},
            {"name": "SOAX", "type": "Residential", "url": "https://soax.com", "note": "Lọc theo bang"},
            {"name": "Bright Data", "type": "Enterprise residential", "url": "https://brightdata.com", "note": "Chất lượng cao, giá cao"},
            {"name": "Oxylabs", "type": "Residential", "url": "https://oxylabs.io", "note": "Ổn định"},
        ],
    },
    "ID": {
        "name": "Indonesia",
        "flag": "🇮🇩",
        "timezone": "Asia/Jakarta",
        "locale": "id_ID",
        "tips": [
            "Proxy Indonesia residential/mobile — tránh SG/VN gán cho nick ID",
            "Warm-up chậm: join group ID, comment tiếng Indonesia",
        ],
        "providers": [
            {"name": "Proxy-Seller", "type": "ID ISP/Residential", "url": "https://proxy-seller.com", "note": "Chọn country ID"},
            {"name": "IPRoyal", "type": "Residential ID", "url": "https://iproyal.com", "note": "Country Indonesia"},
            {"name": "Smartproxy", "type": "Residential ID", "url": "https://smartproxy.com", "note": "Target ID"},
            {"name": "NetNut", "type": "Residential", "url": "https://netnut.io", "note": "Asia coverage"},
        ],
    },
    "PH": {
        "name": "Philippines",
        "flag": "🇵🇭",
        "timezone": "Asia/Manila",
        "locale": "en_PH",
        "tips": ["Proxy PH residential", "Locale en_PH / timezone Manila"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential PH", "url": "https://iproyal.com", "note": "Country PH"},
            {"name": "Smartproxy", "type": "Residential PH", "url": "https://smartproxy.com", "note": "PH targeting"},
            {"name": "Proxy-Seller", "type": "ISP PH", "url": "https://proxy-seller.com", "note": "Philippines"},
        ],
    },
    "TH": {
        "name": "Thailand",
        "flag": "🇹🇭",
        "timezone": "Asia/Bangkok",
        "locale": "th_TH",
        "tips": ["Proxy TH residential", "Không dùng IP VN/SG cho nick Thái"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential TH", "url": "https://iproyal.com", "note": "Thailand"},
            {"name": "SOAX", "type": "Residential TH", "url": "https://soax.com", "note": "TH cities"},
            {"name": "Proxy-Seller", "type": "ISP TH", "url": "https://proxy-seller.com", "note": "Thailand"},
        ],
    },
    "MY": {
        "name": "Malaysia",
        "flag": "🇲🇾",
        "timezone": "Asia/Kuala_Lumpur",
        "locale": "ms_MY",
        "tips": ["Proxy MY residential"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential MY", "url": "https://iproyal.com", "note": "Malaysia"},
            {"name": "Smartproxy", "type": "Residential MY", "url": "https://smartproxy.com", "note": "MY"},
        ],
    },
    "IN": {
        "name": "India",
        "flag": "🇮🇳",
        "timezone": "Asia/Kolkata",
        "locale": "en_IN",
        "tips": ["Proxy IN residential", "Sticky session khi login"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential IN", "url": "https://iproyal.com", "note": "India"},
            {"name": "Smartproxy", "type": "Residential IN", "url": "https://smartproxy.com", "note": "IN cities"},
            {"name": "Bright Data", "type": "Residential IN", "url": "https://brightdata.com", "note": "Coverage rộng"},
        ],
    },
    "BR": {
        "name": "Brazil",
        "flag": "🇧🇷",
        "timezone": "America/Sao_Paulo",
        "locale": "pt_BR",
        "tips": ["Proxy BR residential", "Locale pt_BR"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential BR", "url": "https://iproyal.com", "note": "Brazil"},
            {"name": "Smartproxy", "type": "Residential BR", "url": "https://smartproxy.com", "note": "BR"},
            {"name": "SOAX", "type": "Residential BR", "url": "https://soax.com", "note": "Brazil"},
        ],
    },
    "GB": {
        "name": "United Kingdom",
        "flag": "🇬🇧",
        "timezone": "Europe/London",
        "locale": "en_GB",
        "tips": ["Proxy UK residential"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential UK", "url": "https://iproyal.com", "note": "UK"},
            {"name": "Smartproxy", "type": "Residential UK", "url": "https://smartproxy.com", "note": "GB"},
        ],
    },
    "DE": {
        "name": "Germany",
        "flag": "🇩🇪",
        "timezone": "Europe/Berlin",
        "locale": "de_DE",
        "tips": ["Proxy DE residential"],
        "providers": [
            {"name": "IPRoyal", "type": "Residential DE", "url": "https://iproyal.com", "note": "Germany"},
            {"name": "Bright Data", "type": "Residential DE", "url": "https://brightdata.com", "note": "DE"},
        ],
    },
    "SG": {
        "name": "Singapore",
        "flag": "🇸🇬",
        "timezone": "Asia/Singapore",
        "locale": "en_SG",
        "tips": [
            "IP SG thường datacenter — dễ checkpoint nếu nick “đời thường”",
            "Chỉ dùng nếu nick thật sự gắn SG; không dùng SG cho nick VN/ID",
        ],
        "providers": [
            {"name": "IPRoyal", "type": "Residential SG", "url": "https://iproyal.com", "note": "Singapore"},
            {"name": "Proxy-Seller", "type": "ISP SG", "url": "https://proxy-seller.com", "note": "SG"},
        ],
    },
    "OTHER": {
        "name": "Khác / Chưa xác định",
        "flag": "🌍",
        "timezone": "UTC",
        "locale": "en_US",
        "tips": [
            "Chọn đúng quốc gia nick (profile / SĐT / ngôn ngữ) rồi mua proxy country đó",
            "Không chạy nick khi proxy lệch quốc gia — tỷ lệ checkpoint rất cao",
        ],
        "providers": [
            {"name": "IPRoyal", "type": "Multi-country", "url": "https://iproyal.com", "note": "Chọn country sau"},
            {"name": "Smartproxy", "type": "Multi-country", "url": "https://smartproxy.com", "note": "Target country"},
            {"name": "Proxy-Seller", "type": "ISP nhiều nước", "url": "https://proxy-seller.com", "note": "Chọn quốc gia"},
        ],
    },
}

# SĐT → country
PHONE_CC = {
    "84": "VN", "1": "US", "62": "ID", "63": "PH", "66": "TH", "60": "MY",
    "91": "IN", "55": "BR", "44": "GB", "49": "DE", "65": "SG", "81": "JP",
    "82": "KR", "86": "CN", "61": "AU", "7": "RU", "33": "FR", "34": "ES",
}

EMAIL_TLD_CC = {
    ".vn": "VN", ".id": "ID", ".ph": "PH", ".th": "TH", ".my": "MY",
    ".in": "IN", ".br": "BR", ".uk": "GB", ".de": "DE", ".sg": "SG",
    ".jp": "JP", ".kr": "KR", ".au": "AU", ".fr": "FR",
}

# Tên thường gặp Việt (mẫu) — dùng khi planned_name / full_name
_VN_NAME_HINT = re.compile(
    r"\b(nguyễn|nguyen|trần|tran|lê|le|phạm|pham|hoàng|hoang|huỳnh|huynh|"
    r"phan|vũ|vu|võ|vo|đặng|dang|bùi|bui|đỗ|do|hồ|ho|ngô|ngo|dương|duong|"
    r"lý|ly|thị|thi|văn|van|minh|anh|hương|huong|linh|hà|ha)\b",
    re.I,
)
_ID_NAME_HINT = re.compile(
    r"\b(putri|budi|sari|wahyu|agus|dewi|rizki|indah|pratama|saputra|wijaya)\b", re.I
)
_TH_NAME_HINT = re.compile(r"[\u0e00-\u0e7f]")  # Thai script


def _extract_ip_from_proxy(proxy_str: str) -> str:
    s = (proxy_str or "").strip()
    if not s:
        return ""
    # user:pass@ip:port
    m = re.search(r"@(\d{1,3}(?:\.\d{1,3}){3})", s)
    if m:
        return m.group(1)
    m = re.match(r"^(\d{1,3}(?:\.\d{1,3}){3})", s)
    return m.group(1) if m else ""


_geo_cache = {}  # ip -> {countryCode, country, city}


def lookup_ip_country(ip: str, timeout=0.8) -> dict:
    """
    Geo IP (ip-api.com) — CHẬM / phụ thuộc mạng.
    Timeout rất ngắn; luôn cache. Mặc định app KHÔNG gọi hàm này.
    """
    ip = (ip or "").strip()
    if not ip:
        return {}
    if ip in _geo_cache:
        return _geo_cache.get(ip) or {}
    try:
        url = f"http://ip-api.com/json/{ip}?fields=status,country,countryCode,city,query"
        req = urllib.request.Request(url, headers={"User-Agent": "AI-Maketing-FB/1.0"})
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.loads(resp.read().decode("utf-8", errors="replace"))
        if data.get("status") == "success":
            out = {
                "code": (data.get("countryCode") or "OTHER").upper(),
                "name": data.get("country") or "",
                "city": data.get("city") or "",
                "ip": ip,
            }
            _geo_cache[ip] = out
            return out
    except Exception:
        pass
    _geo_cache[ip] = {}
    return {}


def detect_country_from_phone(phone: str) -> str:
    p = re.sub(r"[\s\-\(\)]", "", phone or "")
    if not p:
        return ""
    if p.startswith("+"):
        p = p[1:]
    if p.startswith("00"):
        p = p[2:]
    # longest CC first
    for length in (3, 2, 1):
        cc = p[:length]
        if cc in PHONE_CC:
            return PHONE_CC[cc]
    return ""


def detect_country_from_email(email: str) -> str:
    e = (email or "").lower().strip()
    if not e or "@" not in e:
        return ""
    domain = e.split("@", 1)[-1]
    for tld, code in EMAIL_TLD_CC.items():
        if domain.endswith(tld):
            return code
    # hotmail/outlook/gmail → không suy ra nước
    return ""


def detect_country_from_name(name: str) -> str:
    n = name or ""
    if _TH_NAME_HINT.search(n):
        return "TH"
    if _VN_NAME_HINT.search(n):
        return "VN"
    if _ID_NAME_HINT.search(n):
        return "ID"
    return ""


def detect_account_country(acc: dict, use_proxy_geo: bool = True) -> dict:
    """
    Nhận dạng quốc gia gợi ý cho 1 nick.
    Trả: {code, name, confidence, reasons[]}
    """
    reasons = []
    votes = Counter()

    # 1) user set thủ công
    manual = (acc.get("country") or acc.get("geo_country") or "").strip().upper()
    if manual and manual in PROXY_PROVIDERS:
        return {
            "code": manual,
            "name": PROXY_PROVIDERS[manual]["name"],
            "confidence": "high",
            "reasons": ["Người dùng chọn sẵn"],
            "flag": PROXY_PROVIDERS[manual]["flag"],
        }

    # 2) phone
    for key in ("phone", "sdt", "mobile"):
        code = detect_country_from_phone(str(acc.get(key) or ""))
        if code:
            votes[code] += 3
            reasons.append(f"SĐT → {code}")
            break

    # 3) name
    for key in ("planned_name", "display_name", "full_name", "name"):
        code = detect_country_from_name(str(acc.get(key) or ""))
        if code:
            votes[code] += 2
            reasons.append(f"Tên profile gợi ý {code}")
            break

    # 4) email TLD
    code = detect_country_from_email(str(acc.get("email") or ""))
    if code:
        votes[code] += 2
        reasons.append(f"Email domain → {code}")

    # 5) proxy IP geo
    if use_proxy_geo:
        ip = _extract_ip_from_proxy(acc.get("proxy") or "")
        if ip:
            geo = lookup_ip_country(ip)
            if geo.get("code"):
                c = geo["code"]
                if c not in PROXY_PROVIDERS:
                    c = "OTHER"
                votes[c] += 2
                city = geo.get("city") or ""
                reasons.append(f"IP proxy {ip} → {geo.get('name')} {city}".strip())

    # 6) note — chỉ khớp TÊN nước đầy đủ (tránh "ID"/"DE" dính nhầm trong chữ)
    blob = " ".join(
        str(acc.get(k) or "")
        for k in ("scenario_note", "note", "login_status")
    ).lower()
    for code, meta in PROXY_PROVIDERS.items():
        if code == "OTHER":
            continue
        name_l = (meta.get("name") or "").lower()
        if name_l and len(name_l) >= 4 and name_l in blob:
            votes[code] += 1
            reasons.append(f"Ghi chú chứa {meta['name']}")

    if not votes:
        return {
            "code": "OTHER",
            "name": PROXY_PROVIDERS["OTHER"]["name"],
            "confidence": "low",
            "reasons": ["Chưa đủ dấu hiệu — hãy chọn quốc gia thủ công"],
            "flag": "🌍",
        }

    code, score = votes.most_common(1)[0]
    total = sum(votes.values())
    conf = "high" if score >= 3 and score / total >= 0.5 else ("medium" if score >= 2 else "low")
    if code not in PROXY_PROVIDERS:
        code = "OTHER"
    return {
        "code": code,
        "name": PROXY_PROVIDERS[code]["name"],
        "confidence": conf,
        "reasons": reasons or ["Heuristic"],
        "flag": PROXY_PROVIDERS[code]["flag"],
        "votes": dict(votes),
    }


def _conf_score(conf: str) -> int:
    return {"high": 3, "medium": 2, "low": 1}.get(conf or "", 0)


def analyze_accounts(
    accounts: list,
    max_geo_lookups: int = 0,
    progress_cb=None,
    use_geo_ip: bool = False,
) -> dict:
    """
    Phân tích list nick → phân bố quốc gia + gợi ý mua proxy.

    NHANH (mặc định):
      - Chỉ dùng tên / SĐT / email / format UID — KHÔNG gọi mạng
      - Cùng 1 nước (hoặc lô đồng dạng) → chỉ lấy 1 nick đại diện, gán cả nhóm

    use_geo_ip=True: mới gọi IP geo (chậm, timeout ngắn).

    progress_cb(done, total, message) — loading UI
    """
    accounts = list(accounts or [])
    n = len(accounts)
    empty = {
        "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "total": 0,
        "distribution": {},
        "primary_country": "OTHER",
        "primary_meta": PROXY_PROVIDERS["OTHER"],
        "accounts": [],
        "providers": PROXY_PROVIDERS["OTHER"]["providers"],
        "tips": PROXY_PROVIDERS["OTHER"]["tips"],
        "sampled": False,
        "sample_count": 0,
        "note": "",
    }
    if not accounts:
        return empty

    def _prog(done, total, msg):
        if callable(progress_cb):
            try:
                progress_cb(done, total, msg)
            except Exception:
                pass

    # --- Bước 1: lọc nhanh 100% local (không mạng) ---
    _prog(0, n, f"Lọc nhanh {n} nick (không gọi mạng)…")
    quick = []
    # batch process — không progress từng nick để tránh spam UI
    for acc in accounts:
        info = detect_account_country(acc, use_proxy_geo=False)
        quick.append(info)
    _prog(n, n, f"Đã lọc {n} nick")

    clear_codes = [q["code"] for q in quick if q.get("code") and q["code"] != "OTHER"]
    unique_clear = set(clear_codes)

    # Đồng dạng lô nick? (UID prefix / email domain)
    uid_pref = Counter()
    email_dom = Counter()
    for a in accounts:
        u = str(a.get("uid") or "")
        if u.isdigit() and len(u) >= 4:
            uid_pref[u[:4]] += 1
        else:
            uid_pref["x"] += 1
        d = str(a.get("email") or "").split("@")[-1].lower()
        email_dom[d or "x"] += 1
    format_same = False
    if n > 0:
        top_uid = uid_pref.most_common(1)[0][1] / n
        top_dom = email_dom.most_common(1)[0][1] / n
        format_same = top_uid >= 0.8 or top_dom >= 0.8

    # Cùng 1 nước? OTHER = chưa biết, không tính “nước khác”
    same_country = False
    batch_code = "OTHER"
    if len(unique_clear) == 1:
        batch_code = next(iter(unique_clear))
        cnt = len(clear_codes)
        ratio = cnt / n
        if cnt >= 2 or ratio >= 0.2 or format_same or cnt == n:
            same_country = True
    elif len(unique_clear) == 0 and n >= 1:
        same_country = True
        batch_code = "OTHER"

    results = []
    note = ""
    sampled = False
    sample_count = n

    # Geo chỉ khi user bật VÀ còn mơ hồ
    allow_geo = bool(use_geo_ip) and max_geo_lookups > 0

    if same_country and n >= 1:
        sampled = n > 1
        sample_count = 1
        best_i = max(
            range(n),
            key=lambda i: (
                0 if quick[i].get("code") == "OTHER" else 1,
                _conf_score(quick[i].get("confidence")),
            ),
        )
        rep = accounts[best_i]
        q0 = quick[best_i]
        _prog(1, 1, f"Đồng nhất {batch_code} — dùng 1 nick đại diện…")

        # Mặc định: dùng kết quả local, KHÔNG gọi geo
        deep = q0
        if allow_geo and (
            q0.get("code") == "OTHER" or _conf_score(q0.get("confidence")) < 2
        ):
            _prog(1, 1, "Đang geo IP (tuỳ chọn, có thể chậm)…")
            deep = detect_account_country(rep, use_proxy_geo=True)

        code = deep["code"] if deep.get("code") and deep["code"] != "OTHER" else batch_code
        if code not in PROXY_PROVIDERS:
            code = "OTHER"
        meta = PROXY_PROVIDERS[code]
        conf = deep.get("confidence") or q0.get("confidence") or "medium"
        base_reasons = [
            f"Nhóm {n} nick → chỉ 1 nick đại diện ({rep.get('uid')})",
        ] + list(deep.get("reasons") or q0.get("reasons") or [])[:3]

        for acc, q in zip(accounts, quick):
            info = {
                "code": code,
                "name": meta["name"],
                "confidence": conf,
                "reasons": base_reasons,
                "flag": meta["flag"],
            }
            acc["_detected_country"] = code
            acc["_detected_country_name"] = meta["name"]
            results.append({"uid": acc.get("uid"), "email": acc.get("email"), **info})

        note = (
            f"⚡ Nhanh (0 mạng): {n} nick → 1 đại diện · nước {code}"
            if not allow_geo
            else f"⚡ {n} nick → 1 đại diện · nước {code} (có geo IP)"
        )
        _prog(n, n, "Xong")
    else:
        # Trộn nhiều nước — vẫn local trước; geo chỉ cho nick OTHER
        geo_used = 0
        seen_proxy_ip = set()
        for i, acc in enumerate(accounts):
            if i == 0 or (i + 1) % 10 == 0 or i + 1 == n:
                _prog(i + 1, n, f"Phân tích {i + 1}/{n}…")
            q = quick[i]
            if q.get("code") != "OTHER" and _conf_score(q.get("confidence")) >= 2:
                info = q
            elif allow_geo and geo_used < max_geo_lookups:
                ip = _extract_ip_from_proxy(acc.get("proxy") or "")
                use_geo = bool(ip and ip not in seen_proxy_ip)
                if use_geo:
                    seen_proxy_ip.add(ip)
                info = detect_account_country(acc, use_proxy_geo=use_geo)
                if use_geo and any("IP proxy" in r for r in info.get("reasons") or []):
                    geo_used += 1
            else:
                info = q
            acc["_detected_country"] = info["code"]
            acc["_detected_country_name"] = info["name"]
            results.append({"uid": acc.get("uid"), "email": acc.get("email"), **info})
        note = (
            f"Đã phân tích {n} nick (local"
            + (f", geo≤{max_geo_lookups}" if allow_geo else ", không geo IP")
            + ")."
        )
        sample_count = n
        _prog(n, n, "Xong")

    dist = Counter(r["code"] for r in results)
    primary = dist.most_common(1)[0][0] if dist else "OTHER"
    return {
        "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "total": len(results),
        "distribution": dict(dist),
        "primary_country": primary,
        "primary_meta": PROXY_PROVIDERS.get(primary, PROXY_PROVIDERS["OTHER"]),
        "accounts": results,
        "providers": PROXY_PROVIDERS.get(primary, PROXY_PROVIDERS["OTHER"])["providers"],
        "tips": PROXY_PROVIDERS.get(primary, PROXY_PROVIDERS["OTHER"])["tips"],
        "sampled": sampled,
        "sample_count": sample_count,
        "total_input": n,
        "note": note,
    }


def providers_for_country(code: str) -> dict:
    code = (code or "OTHER").upper()
    return PROXY_PROVIDERS.get(code, PROXY_PROVIDERS["OTHER"])


def format_report_text(analysis: dict) -> str:
    lines = [
        f"# Báo cáo định vị nick FB — {analysis.get('time')}",
        f"Tổng nick: {analysis.get('total')}",
        f"Quốc gia chính (gợi ý mua proxy): {analysis.get('primary_country')} "
        f"— {analysis.get('primary_meta', {}).get('name')}",
    ]
    if analysis.get("note"):
        lines.append(f"Ghi chú: {analysis.get('note')}")
    if analysis.get("sampled"):
        lines.append(
            f"Tối ưu: phân tích sâu {analysis.get('sample_count', 1)}/"
            f"{analysis.get('total_input') or analysis.get('total')} nick (cùng nước)."
        )
    lines.extend([
        "",
        "## Phân bố",
    ])
    for code, n in sorted((analysis.get("distribution") or {}).items(), key=lambda x: -x[1]):
        meta = PROXY_PROVIDERS.get(code, PROXY_PROVIDERS["OTHER"])
        lines.append(f"  {meta.get('flag', '')} {code} {meta.get('name')}: {n} nick")
    lines.append("")
    lines.append("## Nhà cung cấp proxy gợi ý")
    for p in analysis.get("providers") or []:
        lines.append(f"  - {p['name']} ({p['type']}): {p['url']} — {p.get('note', '')}")
    lines.append("")
    lines.append("## Tips giảm checkpoint")
    for t in analysis.get("tips") or []:
        lines.append(f"  • {t}")
    lines.append("  • Mua proxy → gán 1 proxy / 2–3 nick → RỒI mới login/chạy kịch bản")
    lines.append("  • Không dùng proxy nước A cho nick nước B")
    return "\n".join(lines) + "\n"


def parse_pasted_accounts(raw_text: str) -> list:
    """
    Parse nick dán tay. Hỗ trợ:
      uid|pass|2fa|email|cookie
      uid|pass|email|2fa
      uid pass email
      email:pass
    """
    out = []
    seen = set()
    for line in (raw_text or "").splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        parts = re.split(r"[|\t]+", line) if ("|" in line or "\t" in line) else line.split()
        parts = [p.strip() for p in parts if p.strip()]
        if not parts:
            continue
        uid = email = password = two_fa = cookie = ""
        if len(parts) == 1 and "@" in parts[0] and ":" in parts[0]:
            email, password = parts[0].split(":", 1)
            uid = email
        elif len(parts) >= 2:
            uid = parts[0]
            password = parts[1] if len(parts) > 1 else ""
            # heuristic: email / 2fa / cookie
            for p in parts[2:]:
                pl = p.lower()
                if "@" in p and not email:
                    email = p
                elif re.fullmatch(r"[A-Z2-7]{10,}", p.replace(" ", ""), re.I) and not two_fa:
                    two_fa = p.replace(" ", "").upper()
                elif "c_user=" in pl or "xs=" in pl:
                    cookie = p
                elif not two_fa and len(p) >= 16 and p.replace(" ", "").isalnum():
                    two_fa = p.replace(" ", "").upper()
                elif not email and "@" in p:
                    email = p
        else:
            uid = parts[0]
        if not uid:
            continue
        key = str(uid)
        if key in seen:
            continue
        seen.add(key)
        out.append({
            "uid": str(uid),
            "email": email or (uid if "@" in str(uid) else ""),
            "password": password,
            "two_fa": two_fa,
            "cookie": cookie,
            "proxy": "",
            "status": "Chưa check",
            "is_new": True,
            "joined_groups": [],
            "posts": [],
            "login_status": "Import định vị",
            "planned_name": "",
            "pool": "active",
        })
    return out


# ---------------------------------------------------------------------------
# Tk wizard UI (TRONG APP — sau khi mở app)
# ---------------------------------------------------------------------------
def show_geo_proxy_wizard(
    parent,
    accounts,
    on_continue=None,
    on_save_settings=None,
    settings=None,
    get_selected_accounts=None,
    in_app=True,
):
    """
    Cửa sổ trong app:
      - Nguồn A: nick đã có (tất cả / đang sống / đang chọn trên bảng)
      - Nguồn B: dán / nhập nick mới để nhận dạng
      → Gợi ý NCC proxy theo nước
    """
    import tkinter as tk
    from tkinter import ttk, messagebox, scrolledtext
    import webbrowser

    try:
        from ui_contrast import solid_button, setup_contrast_ttk
    except ImportError:
        import sys
        from pathlib import Path
        sys.path.insert(0, str(Path(__file__).resolve().parent))
        from ui_contrast import solid_button, setup_contrast_ttk

    settings = dict(settings or {})
    win = tk.Toplevel(parent) if parent else tk.Tk()
    win.title("Định vị nick FB + Gợi ý mua Proxy")
    win.geometry("960x720")
    win.minsize(860, 600)
    win.configure(bg="#e2e8f0")
    try:
        setup_contrast_ttk(win)
    except Exception:
        pass
    try:
        win.transient(parent)
        win.grab_set()
    except Exception:
        pass

    state = {
        "analysis": None,
        "country": settings.get("preferred_country") or "VN",
        "busy": False,
        "work_list": [],       # list acc đang phân tích
        "parsed_new": [],      # nick dán mới
    }

    head = tk.Frame(win, bg="#1e3a8a")
    head.pack(fill="x")
    title = (
        "🌍 Định vị nick + Gợi ý proxy (trong app)"
        if in_app
        else "🌍 Chuẩn bị proxy đúng quốc gia"
    )
    tk.Label(
        head, text=title, font=("Helvetica", 15, "bold"), bg="#1e3a8a", fg="#ffffff",
    ).pack(anchor="w", padx=16, pady=(12, 2))
    tk.Label(
        head,
        text="Chọn nick đã có trong app HOẶC dán nick mới → nhận dạng nước → mua proxy cùng khu vực → gán → mới chạy (giảm checkpoint).",
        font=("Helvetica", 10),
        bg="#1e3a8a",
        fg="#bfdbfe",
        wraplength=920,
        justify="left",
    ).pack(anchor="w", padx=16, pady=(0, 12))

    body = tk.Frame(win, bg="#e2e8f0")
    body.pack(fill="both", expand=True, padx=12, pady=8)

    left = tk.Frame(body, bg="#e2e8f0", width=300)
    left.pack(side="left", fill="y", padx=(0, 8))
    left.pack_propagate(False)

    # --- Nguồn dữ liệu ---
    tk.Label(
        left, text="1. Nguồn nick cần check", bg="#e2e8f0", fg="#0f172a",
        font=("Helvetica", 11, "bold"),
    ).pack(anchor="w")
    var_src = tk.StringVar(value="existing_live")
    for val, lab in (
        ("existing_live", "Nick đang sống trong app"),
        ("existing_all", "Tất cả nick trong app"),
        ("existing_selected", "Nick đang chọn (tab Nick FB)"),
        ("paste_new", "Nhập / dán nick MỚI"),
    ):
        tk.Radiobutton(
            left, text=lab, variable=var_src, value=val,
            bg="#e2e8f0", fg="#0f172a", anchor="w",
            font=("Helvetica", 10),
        ).pack(anchor="w")

    n_exist = len(accounts or [])
    tk.Label(
        left, text=f"Hiện có trong app: {n_exist} nick",
        bg="#e2e8f0", fg="#64748b", font=("Helvetica", 9),
    ).pack(anchor="w", pady=(2, 6))

    tk.Label(
        left, text="Dán nick mới (mỗi dòng 1 nick):", bg="#e2e8f0", fg="#0f172a",
        font=("Helvetica", 9, "bold"),
    ).pack(anchor="w")
    tk.Label(
        left,
        text="uid|pass|2fa|email  hoặc  uid pass email",
        bg="#e2e8f0", fg="#64748b", font=("Helvetica", 8),
    ).pack(anchor="w")
    paste_box = scrolledtext.ScrolledText(
        left, height=7, width=34, font=("Menlo", 9),
        bg="#ffffff", fg="#000000", relief="solid", borderwidth=1,
    )
    paste_box.pack(fill="x", pady=4)

    # --- Country ---
    tk.Label(
        left, text="2. Quốc gia gợi ý proxy", bg="#e2e8f0", fg="#0f172a",
        font=("Helvetica", 11, "bold"),
    ).pack(anchor="w", pady=(10, 2))
    var_country = tk.StringVar(value=state["country"])
    cb = ttk.Combobox(
        left, textvariable=var_country,
        values=list(PROXY_PROVIDERS.keys()),
        state="readonly", width=12, font=("Helvetica", 12),
    )
    cb.pack(anchor="w", pady=2)
    lbl_cname = tk.Label(left, text="", bg="#e2e8f0", fg="#1e40af", font=("Helvetica", 10, "bold"))
    lbl_cname.pack(anchor="w")

    def _render_providers(code):
        for w in prov_frame.winfo_children():
            w.destroy()
        meta = providers_for_country(code)
        tk.Label(
            prov_frame,
            text=f"Gợi ý mua proxy — {meta['flag']} {meta['name']}",
            bg="#ffffff", fg="#1e3a8a", font=("Helvetica", 10, "bold"),
        ).pack(anchor="w")
        for p in meta.get("providers") or []:
            row = tk.Frame(prov_frame, bg="#f1f5f9")
            row.pack(fill="x", pady=2)
            tk.Label(
                row,
                text=f"  {p['name']}  ·  {p['type']}  ·  {p.get('note', '')}",
                bg="#f1f5f9", fg="#0f172a", font=("Helvetica", 9), anchor="w",
            ).pack(side="left", fill="x", expand=True)
            solid_button(
                row, "Mở", command=lambda u=p["url"]: webbrowser.open(u),
                bg="#312e81", fg="#ffffff", hover="#1e1b4b", border="#1e1b4b",
                height=28, min_width=56, font=("Helvetica", 10, "bold"),
            ).pack(side="right", padx=4, pady=2)
        tips = meta.get("tips") or []
        if tips:
            tk.Label(
                prov_frame, text="Tips: " + " | ".join(tips[:2]),
                bg="#ffffff", fg="#64748b", font=("Helvetica", 8),
                wraplength=580, justify="left",
            ).pack(anchor="w", pady=4)

    def _refresh_cname(*_):
        code = var_country.get() or "OTHER"
        meta = providers_for_country(code)
        lbl_cname.config(text=f"{meta['flag']} {meta['name']}")
        _render_providers(code)

    var_country.trace_add("write", _refresh_cname)

    def _collect_work_list():
        src = var_src.get()
        if src == "paste_new":
            parsed = parse_pasted_accounts(paste_box.get("1.0", "end"))
            state["parsed_new"] = parsed
            return list(parsed)
        accs = list(accounts or [])
        if src == "existing_selected":
            if callable(get_selected_accounts):
                sel = get_selected_accounts() or []
                if not sel:
                    messagebox.showwarning(
                        "Chọn nick",
                        "Hãy chọn nick trên tab Nick FB trước, hoặc đổi nguồn «Nick đang sống».",
                    )
                    return []
                return list(sel)
            return []
        if src == "existing_live":
            # bỏ nick pool dead nếu có flag
            out = []
            for a in accs:
                st = str(a.get("status") or "").upper()
                if a.get("pool") == "dead" or a.get("is_dead"):
                    continue
                if "CHECKPOINT" in st or st in ("DIE", "DEAD"):
                    continue
                out.append(a)
            return out or list(accs)
        return accs

    # Loading UI
    load_frame = tk.Frame(left, bg="#dbeafe", highlightthickness=0)
    lbl_spin = tk.Label(
        load_frame, text="⠋", font=("Helvetica", 16, "bold"),
        bg="#dbeafe", fg="#1d4ed8",
    )
    lbl_spin.pack(side="left", padx=(8, 4), pady=6)
    lbl_load = tk.Label(
        load_frame, text="Đang phân tích…", font=("Helvetica", 9),
        bg="#dbeafe", fg="#1e40af", wraplength=220, justify="left",
    )
    lbl_load.pack(side="left", fill="x", expand=True, padx=(0, 8), pady=6)
    # ẩn mặc định
    # load_frame.pack sau status khi busy

    SPIN_FRAMES = list("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
    state["load_frame_i"] = 0
    state["load_msg"] = "Đang phân tích…"
    state["load_after_id"] = None

    def _stop_loading():
        state["busy"] = False
        try:
            if state.get("load_after_id"):
                win.after_cancel(state["load_after_id"])
        except Exception:
            pass
        state["load_after_id"] = None
        try:
            load_frame.pack_forget()
        except Exception:
            pass
        try:
            btn_scan.set_state("normal")
        except Exception:
            pass

    def _tick_loading():
        if not state.get("busy"):
            return
        state["load_frame_i"] = (state.get("load_frame_i", 0) + 1) % len(SPIN_FRAMES)
        try:
            lbl_spin.config(text=SPIN_FRAMES[state["load_frame_i"]])
            lbl_load.config(text=state.get("load_msg") or "Đang phân tích…")
            status.config(
                text=f"{SPIN_FRAMES[state['load_frame_i']]} {state.get('load_msg') or 'Đang chờ…'}",
                bg="#dbeafe", fg="#1e40af",
            )
        except Exception:
            pass
        try:
            state["load_after_id"] = win.after(90, _tick_loading)
        except Exception:
            pass

    def _start_loading(msg):
        state["busy"] = True
        state["load_msg"] = msg
        state["load_frame_i"] = 0
        try:
            load_frame.pack(fill="x", pady=(6, 4), before=status)
        except Exception:
            try:
                load_frame.pack(fill="x", pady=(6, 4))
            except Exception:
                pass
        try:
            btn_scan.set_state("disabled")
        except Exception:
            pass
        status.config(text=msg, bg="#dbeafe", fg="#1e40af")
        _tick_loading()

    var_use_geo = tk.BooleanVar(value=False)
    tk.Checkbutton(
        left,
        text="Geo IP proxy (chậm, chỉ khi cần)",
        variable=var_use_geo,
        bg="#e2e8f0",
        fg="#0f172a",
        font=("Helvetica", 9),
        anchor="w",
    ).pack(anchor="w", pady=(4, 0))

    def _run_scan():
        if state.get("busy"):
            return
        work = _collect_work_list()
        if not work:
            if var_src.get() == "paste_new":
                messagebox.showwarning("Trống", "Hãy dán ít nhất 1 nick mới vào ô bên trái.")
            return
        state["work_list"] = work
        n_work = len(work)
        use_geo = bool(var_use_geo.get())
        _start_loading(
            f"Phân tích nhanh {n_work} nick"
            + (" + geo IP…" if use_geo else " (local, không mạng)…")
        )

        def progress_cb(done, total, message):
            state["load_msg"] = message or f"{done}/{total}"

        def work_fn():
            try:
                t0 = time.time()
                analysis = analyze_accounts(
                    list(work),
                    max_geo_lookups=1 if use_geo else 0,
                    progress_cb=progress_cb,
                    use_geo_ip=use_geo,
                )
                elapsed = time.time() - t0
                state["analysis"] = analysis
                primary = analysis.get("primary_country") or "OTHER"

                def ui():
                    _stop_loading()
                    var_country.set(primary)
                    _render_providers(primary)
                    report.delete("1.0", "end")
                    src_label = {
                        "existing_live": "nick sống trong app",
                        "existing_all": "tất cả nick app",
                        "existing_selected": "nick đang chọn",
                        "paste_new": "nick dán mới",
                    }.get(var_src.get(), "")
                    report.insert("1.0", f"# Nguồn: {src_label}\n")
                    report.insert("end", f"# Thời gian: {elapsed:.2f}s\n")
                    if analysis.get("note"):
                        report.insert("end", f"# {analysis.get('note')}\n")
                    report.insert("end", format_report_text(analysis))
                    report.insert("end", "\n## Chi tiết nick (tối đa 30 dòng)\n")
                    for r in (analysis.get("accounts") or [])[:30]:
                        report.insert(
                            "end",
                            f"  {r.get('uid')}: {r.get('flag')} {r.get('code')} "
                            f"({r.get('confidence')}) — {', '.join((r.get('reasons') or [])[:2])}\n",
                        )
                    if analysis.get("total", 0) > 30:
                        report.insert("end", f"  … +{analysis['total'] - 30} nick (cùng kết quả nhóm)\n")
                    extra = ""
                    if analysis.get("sampled"):
                        extra = (
                            f" · ⚡ 1/{analysis.get('total_input') or n_work} nick"
                        )
                    status.config(
                        text=(
                            f"✓ {elapsed:.2f}s · {analysis['total']} nick · {primary}{extra}\n"
                            f"→ Mua proxy {primary}, gán 1/2–3 nick."
                        ),
                        bg="#dcfce7", fg="#166534",
                    )

                (parent or win).after(0, ui)
            except Exception as ex:
                def err():
                    _stop_loading()
                    status.config(text=f"Lỗi: {ex}", bg="#fee2e2", fg="#991b1b")
                (parent or win).after(0, err)

        threading.Thread(target=work_fn, daemon=True).start()

    btn_scan = solid_button(
        left, "🔍  Quét & nhận dạng quốc gia",
        command=_run_scan,
        bg="#15803d", fg="#ffffff", hover="#166534", border="#14532d",
        height=40, min_width=260,
    )
    btn_scan.pack(fill="x", pady=(10, 4))

    solid_button(
        left, "🌐  Mở trang mua proxy",
        command=lambda: [
            webbrowser.open(p["url"])
            for p in (providers_for_country(var_country.get() or "OTHER").get("providers") or [])[:2]
        ],
        bg="#1d4ed8", fg="#ffffff", hover="#1e40af", border="#1e3a8a",
        height=36, min_width=260, font=("Helvetica", 10, "bold"),
    ).pack(fill="x", pady=4)

    status = tk.Label(
        left,
        text="Chọn nguồn nick → bấm Quét. Cùng 1 nước → chỉ phân tích 1 nick (nhanh).",
        bg="#fef3c7", fg="#92400e", wraplength=280, justify="left",
        font=("Helvetica", 9), padx=8, pady=8,
    )
    status.pack(fill="x", pady=10)

    # Right
    right = tk.Frame(body, bg="#ffffff", highlightbackground="#94a3b8", highlightthickness=1)
    right.pack(side="left", fill="both", expand=True)

    tk.Label(
        right, text="Kết quả nhận dạng & gợi ý mua proxy",
        bg="#ffffff", fg="#0f172a", font=("Helvetica", 11, "bold"),
    ).pack(anchor="w", padx=10, pady=(10, 4))

    report = scrolledtext.ScrolledText(
        right, height=18, font=("Menlo", 10), bg="#0f172a", fg="#bbf7d0",
        insertbackground="#fff",
    )
    report.pack(fill="both", expand=True, padx=10, pady=4)

    prov_frame = tk.Frame(right, bg="#ffffff")
    prov_frame.pack(fill="x", padx=10, pady=6)

    def _apply_and_close(add_new=False):
        code = var_country.get() or "OTHER"
        payload = {
            "preferred_country": code,
            "geo_wizard_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "geo_wizard_done": True,
            "geo_wizard_force": False,
        }
        # map country updates for existing
        updates = {}
        if state.get("analysis"):
            for r in state["analysis"].get("accounts") or []:
                uid = str(r.get("uid") or "")
                if uid:
                    updates[uid] = {
                        "code": r.get("code"),
                        "name": r.get("name"),
                        "confidence": r.get("confidence"),
                    }
            # also mutate shared account objects
            by_uid = {str(r.get("uid")): r for r in state["analysis"].get("accounts") or []}
            for acc in state.get("work_list") or []:
                r = by_uid.get(str(acc.get("uid")))
                if r:
                    acc["country"] = r.get("code")
                    acc["country_name"] = r.get("name")
                    acc["country_confidence"] = r.get("confidence")
        payload["account_country_updates"] = updates

        if add_new and state.get("parsed_new"):
            # gắn country cho nick mới
            if state.get("analysis"):
                by_uid = {str(r.get("uid")): r for r in state["analysis"].get("accounts") or []}
                for na in state["parsed_new"]:
                    r = by_uid.get(str(na.get("uid")))
                    if r:
                        na["country"] = r.get("code")
                        na["country_name"] = r.get("name")
            payload["new_accounts"] = list(state["parsed_new"])

        if callable(on_save_settings):
            try:
                on_save_settings(payload)
            except Exception:
                pass
        try:
            win.destroy()
        except Exception:
            pass
        if callable(on_continue):
            try:
                on_continue()
            except Exception:
                pass
        msg = f"Đã lưu gợi ý proxy cho nước {code}."
        if add_new and payload.get("new_accounts"):
            msg += f"\nĐã thêm {len(payload['new_accounts'])} nick mới vào app."
        msg += "\nTiếp: mua proxy → proxies.txt → Quét & gán 3 nick/1 proxy."
        try:
            messagebox.showinfo("Định vị proxy", msg)
        except Exception:
            pass

    foot = tk.Frame(win, bg="#e2e8f0")
    foot.pack(fill="x", padx=12, pady=10)
    solid_button(
        foot, "Đóng", command=win.destroy,
        bg="#475569", fg="#ffffff", hover="#334155", border="#1e293b",
        height=36, min_width=90, font=("Helvetica", 10, "bold"),
    ).pack(side="left")
    solid_button(
        foot, "💾 Lưu country + thêm nick mới vào app",
        command=lambda: _apply_and_close(add_new=True),
        bg="#1d4ed8", fg="#ffffff", hover="#1e40af", border="#1e3a8a",
        height=36, min_width=260, font=("Helvetica", 10, "bold"),
    ).pack(side="right", padx=4)
    solid_button(
        foot, "✓ Lưu gợi ý (nick app)",
        command=lambda: _apply_and_close(add_new=False),
        bg="#15803d", fg="#ffffff", hover="#166534", border="#14532d",
        height=36, min_width=180, font=("Helvetica", 10, "bold"),
    ).pack(side="right", padx=4)

    _refresh_cname()
    # không auto-scan nặng; user chủ động bấm
    status.config(
        text="Chọn nguồn (nick app / dán mới) → Quét. Không chặn vào app.",
        bg="#e0e7ff", fg="#312e81",
    )
    return win


if __name__ == "__main__":
    import tkinter as tk

    demo = [
        {"uid": "1", "email": "a@hotmail.com", "planned_name": "Nguyễn Thị Hoa", "proxy": ""},
        {"uid": "2", "email": "b@gmail.com", "planned_name": "Budi Pratama", "proxy": ""},
    ]
    root = tk.Tk()
    root.withdraw()
    show_geo_proxy_wizard(root, demo, on_continue=None, in_app=True)
    root.mainloop()
