#!/usr/bin/env python3
"""
Grok relogin — refresh tokens untuk semua akun.
Mode 1: API refresh (jika refresh_token masih valid)
Mode 2: Browser relogin (jika refresh_token revoked — OAuth PKCE login ulang)

Usage:
  python relogin.py --test              # test current tokens
  python relogin.py --api               # refresh via API (fast, no browser)
  python relogin.py --browser           # relogin via browser (OAuth PKCE)
  python relogin.py --browser --email user@...  # specific email
"""
import argparse
import asyncio
import base64
import hashlib
import json
import os
import secrets
import sys
import time
import urllib.request
import urllib.parse
import urllib.error
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT))
RESULTS_DIR = ROOT / "results"

XAI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828"
XAI_TOKEN_URL = "https://auth.x.ai/oauth2/token"
XAI_AUTHORIZE = "https://auth.x.ai/oauth2/authorize"
XAI_REDIRECT_URI = "http://127.0.0.1:56121/callback"
XAI_SCOPE = "openid profile email offline_access grok-cli:access api:access conversations:read conversations:write"
CLI_CHAT_URL = "https://cli-chat-proxy.grok.com/v1/responses"
CLI_VERSION = "0.2.93"
CLI_UA = f"Grok-CLI/{CLI_VERSION}"


def load_all_accounts():
    accounts = []
    if not RESULTS_DIR.exists():
        return accounts
    for batch_dir in sorted(RESULTS_DIR.iterdir()):
        if not batch_dir.is_dir() or not batch_dir.name.startswith("batch_"):
            continue
        json_path = batch_dir / "accounts.json"
        if not json_path.exists():
            continue
        try:
            data = json.loads(json_path.read_text())
            for acc in data:
                acc["_batch_dir"] = str(batch_dir)
                accounts.append(acc)
        except Exception:
            pass
    return accounts


def test_token(access_tok):
    body = json.dumps({
        "model": "grok-4.5",
        "input": [{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "Reply OK"}]}],
        "stream": False, "store": False, "max_output_tokens": 16,
    }).encode("utf-8")
    headers = {
        "Authorization": f"Bearer {access_tok}",
        "Content-Type": "application/json",
        "Accept": "application/json",
        "User-Agent": CLI_UA,
        "x-xai-token-auth": "xai-grok-cli",
        "x-grok-client-identifier": "grok-cli",
        "x-grok-client-version": CLI_VERSION,
        "x-grok-model-override": "grok-4.5",
    }
    req = urllib.request.Request(CLI_CHAT_URL, data=body, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            rem = resp.headers.get("x-ratelimit-remaining-tokens", "?")
            return {"ok": True, "status": resp.status, "credits": f"{rem}"}
    except urllib.error.HTTPError as e:
        return {"ok": False, "status": e.code, "error": e.read().decode("utf-8", errors="ignore")[:100]}
    except Exception as e:
        return {"ok": False, "status": 0, "error": str(e)}


def refresh_token_api(refresh_tok):
    form = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "client_id": XAI_CLIENT_ID,
        "refresh_token": refresh_tok,
    }).encode("utf-8")
    req = urllib.request.Request(XAI_TOKEN_URL, data=form, headers={
        "Content-Type": "application/x-www-form-urlencoded",
        "Accept": "application/json",
    }, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            return data
    except urllib.error.HTTPError as e:
        return {"error": f"HTTP {e.code}: {e.read().decode('utf-8', errors='ignore')[:100]}"}
    except Exception as e:
        return {"error": str(e)}


def generate_pkce():
    raw = secrets.token_bytes(96)
    verifier = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
    return verifier, challenge


async def relogin_browser(email, password, proxy_url=None):
    """Relogin via browser: OAuth PKCE → capture code → exchange tokens."""
    from cloakbrowser import launch as cb_launch
    from urllib.parse import urlencode, urlparse, parse_qs

    verifier, challenge = generate_pkce()
    state = secrets.token_urlsafe(24)
    nonce = secrets.token_hex(16)

    params = {
        "response_type": "code",
        "client_id": XAI_CLIENT_ID,
        "redirect_uri": XAI_REDIRECT_URI,
        "scope": XAI_SCOPE,
        "code_challenge": challenge,
        "code_challenge_method": "S256",
        "state": state,
        "nonce": nonce,
        "plan": "generic",
        "referrer": "open-grok-build",
    }
    auth_url = f"{XAI_AUTHORIZE}?{urlencode(params)}"

    cb_kwargs = {"headless": True, "humanize": True}
    if proxy_url:
        cb_kwargs["proxy"] = {"server": proxy_url} if isinstance(proxy_url, str) else proxy_url
        cb_kwargs["geoip"] = False
        cb_kwargs["timezone"] = "Asia/Singapore"

    browser = cb_launch(**cb_kwargs)
    context = browser.new_context(viewport={"width": 1280, "height": 900}, locale="en-US")
    page = context.new_page()
    page.set_default_timeout(45000)

    auth_code = {"code": None}

    def _handle_route(route):
        req_url = route.request.url
        if "127.0.0.1:56121" in req_url or "localhost:56121" in req_url or ("/callback" in req_url and "127.0.0.1" in req_url):
            parsed = urlparse(req_url)
            params = parse_qs(parsed.query)
            code = params.get("code", [""])[0]
            if code:
                auth_code["code"] = code
            try:
                page.evaluate("window.stop()")
            except:
                pass
            return
        try:
            route.continue_()
        except:
            pass

    page.route("**/*", _handle_route)

    try:
        page.goto(auth_url, timeout=45000, wait_until="domcontentloaded")
    except:
        pass

    # Wait for login page + fill credentials
    deadline = time.monotonic() + 90
    while time.monotonic() < deadline and not auth_code["code"]:
        url = page.url
        if "accounts.x.ai" in url or "auth.x.ai" in url:
            # Fill email/password if form visible
            try:
                email_input = page.locator('input[type="email"]').first
                if email_input.is_visible(timeout=3000):
                    email_input.fill(email)
                    time.sleep(0.5)
                    pw_input = page.locator('input[type="password"]').first
                    if pw_input.is_visible(timeout=2000):
                        pw_input.fill(password)
                        time.sleep(0.5)
                        pw_input.press("Enter")
                    else:
                        # Click "Login with email" or "Continue" button
                        page.evaluate("""() => {
                            var btns = document.querySelectorAll('button');
                            for (var b of btns) {
                                var t = (b.innerText||'').toLowerCase();
                                if (t.includes('continue') || t.includes('next') || t.includes('login')) {
                                    b.click(); return;
                                }
                            }
                        }""")
                    time.sleep(3)
            except:
                pass

            # Click Allow/Authorize/Continue for consent
            try:
                page.evaluate("""() => {
                    var btns = document.querySelectorAll('button');
                    for (var b of btns) {
                        var t = (b.innerText||'').toLowerCase();
                        if (t.includes('allow') || t.includes('authorize') || t.includes('approve') || t.includes('continue') || t.includes('accept')) {
                            if (!t.includes('google') && !t.includes('deny') && !t.includes('cancel')) {
                                b.click(); return;
                            }
                        }
                    }
                }""")
            except:
                pass

        time.sleep(2)

    browser.close()

    if not auth_code["code"]:
        return {"error": "No OAuth code captured"}

    # Exchange code for tokens
    form = urllib.parse.urlencode({
        "grant_type": "authorization_code",
        "client_id": XAI_CLIENT_ID,
        "code": auth_code["code"],
        "redirect_uri": XAI_REDIRECT_URI,
        "code_verifier": verifier,
    }).encode("utf-8")

    req = urllib.request.Request(XAI_TOKEN_URL, data=form, headers={
        "Content-Type": "application/x-www-form-urlencoded",
        "Accept": "application/json",
    }, method="POST")

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            return data
    except urllib.error.HTTPError as e:
        return {"error": f"Token exchange failed: {e.code} {e.read().decode('utf-8', errors='ignore')[:100]}"}
    except Exception as e:
        return {"error": f"Token exchange error: {e}"}


def main():
    parser = argparse.ArgumentParser(description="Grok relogin")
    parser.add_argument("--test", action="store_true", help="Test current tokens")
    parser.add_argument("--api", action="store_true", help="Refresh via API (no browser)")
    parser.add_argument("--browser", action="store_true", help="Relogin via browser (OAuth PKCE)")
    parser.add_argument("--email", default="", help="Specific email")
    parser.add_argument("--batch", default="all", help="Batch: all, latest, or name")
    args = parser.parse_args()

    if not any([args.test, args.api, args.browser]):
        args.test = True  # Default to test

    accounts = load_all_accounts()
    if not accounts:
        print("No accounts found")
        return

    if args.batch == "latest":
        latest = sorted(set(a["_batch_dir"] for a in accounts))[-1]
        accounts = [a for a in accounts if a["_batch_dir"] == latest]
    elif args.batch != "all":
        accounts = [a for a in accounts if args.batch in a["_batch_dir"]]

    if args.email:
        accounts = [a for a in accounts if args.email in a.get("email", "")]

    print(f"Accounts: {len(accounts)}")
    print("=" * 60)

    ok_count = 0
    for i, acc in enumerate(accounts):
        email = acc.get("email", "?")
        password = acc.get("password", "")
        tokens = acc.get("tokens", {})
        access = tokens.get("access_token", "")
        refresh = tokens.get("refresh_token", "")

        if args.test:
            result = test_token(access)
            status = "✅" if result["ok"] else "❌"
            credits = result.get("credits", "")
            err = result.get("error", "")[:50]
            print(f"  {status} [{i+1}] {email}: {result['status']} credits={credits} {err}")
            if result["ok"]:
                ok_count += 1

        elif args.api:
            result = refresh_token_api(refresh)
            if "error" in result:
                print(f"  ❌ [{i+1}] {email}: {result['error'][:60]}")
            else:
                acc["tokens"]["access_token"] = result["access_token"]
                acc["tokens"]["refresh_token"] = result.get("refresh_token", refresh)
                test = test_token(result["access_token"])
                status = "✅" if test["ok"] else "⚠️"
                print(f"  {status} [{i+1}] {email}: refreshed credits={test.get('credits','')}")
                if test["ok"]:
                    ok_count += 1
            time.sleep(0.5)

        elif args.browser:
            proxy = acc.get("proxy", "")
            if proxy and not proxy.startswith("http://"):
                proxy = f"http://{proxy}"
            result = asyncio.run(relogin_browser(email, password, proxy if proxy and proxy != "direct" else None))
            if "error" in result:
                print(f"  ❌ [{i+1}] {email}: {result['error'][:60]}")
            else:
                acc["tokens"]["access_token"] = result["access_token"]
                acc["tokens"]["refresh_token"] = result.get("refresh_token", refresh)
                test = test_token(result["access_token"])
                status = "✅" if test["ok"] else "⚠️"
                print(f"  {status} [{i+1}] {email}: relogged credits={test.get('credits','')}")
                if test["ok"]:
                    ok_count += 1
            time.sleep(2)

    print(f"\n{'=' * 60}")
    print(f"Result: {ok_count}/{len(accounts)} OK")

    # Save if refreshed
    if args.api or args.browser:
        batch_groups = {}
        for acc in accounts:
            bdir = acc.pop("_batch_dir", "")
            batch_groups.setdefault(bdir, []).append(acc)
        for bdir, accs in batch_groups.items():
            json_path = Path(bdir) / "accounts.json"
            if json_path.exists():
                json_path.write_text(json.dumps(accs, indent=2))
        print(f"Updated {len(batch_groups)} batch files")


if __name__ == "__main__":
    main()
