#!/usr/bin/env python3
"""
Clawdi.ai Full Registration + Subscription API
Modified: Use local CloakBrowser Turnstile-Solver instead of FCB

Complete flow:
1. Capture referral (get anonymous_token + device_token)
2. Create temp email (YYDS)
3. Solve Turnstile (LOCAL CloakBrowser solver at localhost:5000)
4. Register on Clerk API
5. Get verification code (YYDS)
6. Submit verification code
7. Identify referral (link device_token to new user)
8. [Optional] Get plans / create checkout session
"""
import httpx
import json
import time
import re
import random
import string
import uuid
import sys
import datetime

# ============================================================
# CONFIG
# ============================================================
YYDS_API_KEY = "AC-29e9b09c5ae93e1347debb66"
YYDS_BASE = "https://maliapi.215.im/v1"

# Local CloakBrowser Turnstile-Solver
SOLVER_URL = "http://localhost:5000"

CLERK_API = "https://clerk.clawdi.ai"
CLAWDI_API = "https://api.clawdi.ai"
SITEKEY = "0x4AAAAAAAWXJGBD7bONzLBd"
SITEURL = "https://accounts.clawdi.ai/sign-up"
CLERK_JS_VERSION = "6.12.1"
API_VERSION = "2025-04-10"
PASSWORD = "Cl@wdi2026xY7!"
REF_CODE = "AV476KVN"

PREFERRED_DOMAINS = [
    "007.hzeg.eu.org", "0m0.abrdns.com", "0m0.app",
    "3jsfuye.tech", "15768.xyz", "20220108.xyz", "100811.xyz",
]

BROWSER_HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "Accept": "application/json",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": "https://accounts.clawdi.ai",
    "Referer": "https://accounts.clawdi.ai/sign-up",
}

OUTPUT_FILE = "clawdi_ref_accounts.json"


def rand_name(length=10):
    return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))


# ============================================================
# Referral Tracking
# ============================================================
def capture_referral(ref_code, device_token):
    """Capture referral code via Clawdi API"""
    print(f"\n[0] Capturing referral: {ref_code}")
    with httpx.Client(timeout=15) as c:
        r = c.post(f"{CLAWDI_API}/referrals/capture",
            headers={"Content-Type": "application/json"},
            json={"code": ref_code, "device_token": device_token, "capture_surface": "shortlink"})
        if r.status_code in (200, 201):
            data = r.json()
            print(f"  ✅ referrer: {data.get('referrer_display_name')} ({data.get('referrer_user_id')})")
            return data
        print(f"  [!] Capture failed: {r.status_code} {r.text[:200]}")
        return None


def identify_referral(session_token, device_token, anonymous_token=None, ref_code=None):
    """Identify referral with auth session"""
    print(f"\n[7] Identifying referral...")
    with httpx.Client(timeout=15) as c:
        body = {"device_token": device_token, "capture_surface": "onboarding"}
        if anonymous_token:
            body["anonymous_token"] = anonymous_token
        if ref_code:
            body["referral_code"] = ref_code
        r = c.post(f"{CLAWDI_API}/referrals/identify",
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {session_token}"},
            json=body)
        print(f"  Status: {r.status_code} → {r.text[:200]}")
        return r.json() if r.status_code in (200, 201) else None


# ============================================================
# YYDS Mail
# ============================================================
def yyds_create_email(domain=None):
    if domain is None:
        domain = random.choice(PREFERRED_DOMAINS)
    local_part = rand_name(10)
    with httpx.Client(timeout=15) as c:
        r = c.post(f"{YYDS_BASE}/accounts",
            headers={"X-API-Key": YYDS_API_KEY, "Content-Type": "application/json"},
            json={"localPart": local_part, "domain": domain})
        if r.status_code not in (200, 201):
            print(f"  [!] YYDS error: {r.status_code}")
            return None, None, None
        info = r.json()["data"]
        print(f"  📧 {info['address']}")
        return info["address"], info["token"], info["id"]


def yyds_get_code(email, token, max_wait=90):
    print(f"  Waiting for code...")
    start = time.time()
    with httpx.Client(timeout=15) as c:
        while time.time() - start < max_wait:
            time.sleep(5)
            try:
                r = c.get(f"{YYDS_BASE}/messages",
                    headers={"Authorization": f"Bearer {token}"},
                    params={"address": email, "limit": "5"})
                if r.status_code != 200:
                    continue
                msgs = r.json().get("data", {}).get("messages", [])
                for msg in msgs:
                    subj = msg.get("subject", "")
                    codes = re.findall(r'\b(\d{6})\b', subj)
                    if codes and ("verif" in subj.lower() or "code" in subj.lower()):
                        print(f"  ✅ Code: {codes[0]}")
                        return codes[0]
                    mid = msg.get("id")
                    r2 = c.get(f"{YYDS_BASE}/messages/{mid}",
                        headers={"Authorization": f"Bearer {token}"},
                        params={"address": email})
                    if r2.status_code == 200:
                        full = r2.json().get("data", {})
                        all_text = (full.get("text", "") or "") + " " + " ".join(full.get("html", []) or []) + " " + subj
                        codes = re.findall(r'\b(\d{6})\b', all_text)
                        if codes:
                            print(f"  ✅ Code: {codes[0]}")
                            return codes[0]
            except Exception:
                pass
    return None


# ============================================================
# Local CloakBrowser Turnstile Solver
# ============================================================
def solve_turnstile(url=None, sitekey=None, max_wait=120):
    """Solve Turnstile using local CloakBrowser solver at localhost:5000"""
    url = url or SITEURL
    sitekey = sitekey or SITEKEY
    print(f"  [Solver] Solving Turnstile (sitekey: {sitekey[:12]}...)")
    
    with httpx.Client(timeout=20) as c:
        # Submit task
        r = c.post(f"{SOLVER_URL}/turnstile", json={"url": url, "sitekey": sitekey})
        if r.status_code != 202:
            print(f"  [!] Solver submit failed: {r.status_code} {r.text[:200]}")
            return None
        
        task_id = r.json().get("task_id")
        if not task_id:
            print(f"  [!] No task_id: {r.json()}")
            return None
        print(f"  [Solver] Task submitted: {task_id[:8]}...")
        
        # Poll for result
        start = time.time()
        while time.time() - start < max_wait:
            time.sleep(3)
            try:
                rr = c.get(f"{SOLVER_URL}/result", params={"id": task_id}, timeout=10)
                data = rr.json()
                status = data.get("status")
                
                if status == "success":
                    token = data.get("data", {}).get("token", "")
                    elapsed = data.get("data", {}).get("elapsed_time", 0)
                    if token:
                        print(f"  ✅ Turnstile solved ({len(token)} chars, {elapsed:.1f}s)")
                        return token
                    print(f"  [!] Success but no token")
                    return None
                elif status == "error":
                    error = data.get("error", "unknown")
                    print(f"  [!] Solver error: {error}")
                    return None
                elif status == "pending":
                    elapsed = time.time() - start
                    if int(elapsed) % 15 == 0:
                        print(f"  [Solver] Processing... {elapsed:.0f}s")
                else:
                    print(f"  [!] Unknown status: {status}")
                    return None
            except Exception as e:
                print(f"  [!] Poll error: {e}")
        
        print(f"  [!] Solver timeout after {max_wait}s")
        return None


# ============================================================
# Clerk Registration (single-session version)
# ============================================================
def clerk_full_flow(email, password, captcha_token, yyds_token, yyds_address):
    """Complete registration + verification in a single httpx.Client session."""
    with httpx.Client(base_url=CLERK_API, follow_redirects=True, timeout=30,
                      headers=BROWSER_HEADERS) as client:
        params = {"__clerk_api_version": API_VERSION, "_clerk_js_version": CLERK_JS_VERSION}

        # Init client
        client.get("/v1/client", params=params)

        # Sign up
        r = client.post("/v1/client/sign_ups", params=params, data={
            "email_address": email,
            "password": password,
            "captcha_token": captcha_token,
        })
        data = r.json()
        if data.get("errors"):
            print(f"  [!] Signup: {data['errors'][0].get('code')} - {data['errors'][0].get('message', '')[:80]}")
            return None

        resp = data["response"]
        sign_up_id = resp["id"]
        print(f"  signUp: {sign_up_id} ({resp.get('status')})")

        # Prepare verification
        if "email_address" in (resp.get("unverified_fields") or []):
            client.post(f"/v1/client/sign_ups/{sign_up_id}/prepare_verification",
                params=params, data={"strategy": "email_code"})
            print(f"  Verification email sent")

        # Wait for code
        print(f"  Waiting for verification code...")
        code = yyds_get_code(yyds_address, yyds_token)
        if not code:
            print(f"  [!] No code received")
            return None

        # Submit verification (same session!)
        print(f"  Submitting code: {code}")
        r = client.post(f"/v1/client/sign_ups/{sign_up_id}/attempt_verification",
            params=params, data={"strategy": "email_code", "code": code})
        vdata = r.json()
        if vdata.get("errors"):
            print(f"  [!] Verify: {vdata['errors'][0].get('message', '')[:80]}")
            return None

        vresp = vdata["response"]
        uid = vresp.get("created_user_id")
        print(f"  ✅ Verified! uid: {uid}")

        # Extract session token
        session_token = None
        sessions = vdata.get("client", {}).get("sessions", [])
        if sessions:
            session_token = sessions[0].get("last_active_token", {}).get("jwt", "")

        return {
            "uid": uid,
            "session_token": session_token,
            "sign_up_id": sign_up_id,
        }


# ============================================================
# Clawdi Subscription API
# ============================================================
def clawdi_get_plans():
    """Get all plans (no auth required)"""
    with httpx.Client(timeout=15) as c:
        r = c.get(f"{CLAWDI_API}/subscription/plans")
        if r.status_code == 200:
            return r.json()
        return None


def clawdi_get_subscription(session_token):
    """Get current subscription"""
    with httpx.Client(timeout=15) as c:
        r = c.get(f"{CLAWDI_API}/subscription/current",
            headers={"Authorization": f"Bearer {session_token}"})
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clawdi_activation_fee(session_token):
    """Get activation fee status"""
    with httpx.Client(timeout=15) as c:
        r = c.get(f"{CLAWDI_API}/subscription/activation-fee",
            headers={"Authorization": f"Bearer {session_token}"})
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clawdi_checkout(session_token, plan_slug="freemium", ui_mode="custom",
                     billing_term_months=1, collection_method="charge_automatically"):
    """Create a Stripe checkout session."""
    with httpx.Client(timeout=20) as c:
        r = c.post(f"{CLAWDI_API}/subscription/checkout",
            headers={"Authorization": f"Bearer {session_token}", "Content-Type": "application/json"},
            json={
                "plan_slug": plan_slug,
                "billing_term_months": billing_term_months,
                "collection_method": collection_method,
                "ui_mode": ui_mode,
            })
        return r.status_code, r.json() if r.status_code == 200 else r.text[:500]


def clawdi_billing_portal(session_token):
    """Get Stripe billing portal URL"""
    with httpx.Client(timeout=15) as c:
        r = c.post(f"{CLAWDI_API}/subscription/portal",
            headers={"Authorization": f"Bearer {session_token}", "Content-Type": "application/json"},
            json={})
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clawdi_billing_history(session_token):
    """Get billing history"""
    with httpx.Client(timeout=15) as c:
        r = c.get(f"{CLAWDI_API}/subscription/billing/history",
            headers={"Authorization": f"Bearer {session_token}"})
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clawdi_redeem_preview(code, turnstile_token=None):
    """Preview a promo/redeem code"""
    with httpx.Client(timeout=15) as c:
        body = {"code": code}
        if turnstile_token:
            body["turnstile_token"] = turnstile_token
        r = c.post(f"{CLAWDI_API}/subscription/redeem/preview",
            headers={"Content-Type": "application/json"},
            json=body)
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clawdi_redeem(session_token, code, turnstile_token=None, idempotency_key=None,
                   locale=None, deploy_config=None):
    """Redeem a promo code"""
    with httpx.Client(timeout=20) as c:
        headers = {"Authorization": f"Bearer {session_token}", "Content-Type": "application/json"}
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key
        body = {"code": code}
        if turnstile_token:
            body["turnstile_token"] = turnstile_token
        if locale:
            body["locale"] = locale
        if deploy_config:
            body["deploy_config"] = deploy_config
        r = c.post(f"{CLAWDI_API}/subscription/redeem", headers=headers, json=body)
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


def clerk_login_with_2fa(email, password, yyds_api_key=None, yyds_address=None):
    """Login via Clerk API, handling needs_client_trust (email 2FA)."""
    if yyds_api_key is None:
        yyds_api_key = YYDS_API_KEY
    
    with httpx.Client(base_url=CLERK_API, follow_redirects=True, timeout=30,
                      headers=BROWSER_HEADERS) as client:
        params = {"__clerk_api_version": API_VERSION, "_clerk_js_version": CLERK_JS_VERSION}
        client.get("/v1/client", params=params)
        
        r = client.post("/v1/client/sign_ins", params=params, data={
            "identifier": email,
            "password": password,
        })
        data = r.json()
        resp = data.get("response", data)
        status = resp.get("status") if isinstance(resp, dict) else None
        sid = resp.get("id") if isinstance(resp, dict) else None
        
        if status == "complete":
            sessions = data.get("client", {}).get("sessions", [])
            st = sessions[0].get("last_active_token", {}).get("jwt", "") if sessions else ""
            print(f"  ✅ Direct login: {email}")
            return st
        
        if status != "needs_client_trust":
            print(f"  [!] Unexpected status: {status}")
            return None
        
        print(f"  needs_client_trust — preparing email 2FA...")
        
        eid = None
        for f in (resp.get("supported_second_factors") or []):
            if f.get("strategy") == "email_code":
                eid = f.get("email_address_id"); break
        if not eid:
            for f in (resp.get("supported_first_factors") or []):
                if f.get("strategy") == "email_code":
                    eid = f.get("email_address_id"); break
        if not eid:
            print(f"  [!] No email_code factor found")
            return None
        
        r2 = client.post(f"/v1/client/sign_ins/{sid}/prepare_second_factor", params=params,
            data={"strategy": "email_code", "email_address_id": eid})
        if r2.status_code != 200:
            print(f"  [!] prepare_second_factor: {r2.status_code} {r2.text[:200]}")
            return None
        print(f"  ✅ 2FA code sent to email")
        
        code = None; t0 = time.time()
        with httpx.Client(timeout=15) as mc:
            while time.time() - t0 < 60:
                time.sleep(5)
                try:
                    r = mc.get(f"{YYDS_BASE}/messages",
                        headers={"X-API-Key": yyds_api_key},
                        params={"address": yyds_address, "limit": "5"})
                    msgs = r.json().get("data", {}).get("messages", [])
                    for msg in msgs:
                        subj = msg.get("subject", "")
                        codes = re.findall(r'\b(\d{6})\b', subj)
                        if codes:
                            code = codes[0]; break
                        mid = msg.get("id")
                        r3 = mc.get(f"{YYDS_BASE}/messages/{mid}",
                            headers={"X-API-Key": yyds_api_key},
                            params={"address": yyds_address})
                        if r3.status_code == 200:
                            full = r3.json().get("data", {})
                            all_text = (full.get("text", "") or "") + " " + subj
                            codes2 = re.findall(r'\b(\d{6})\b', all_text)
                            if codes2: code = codes2[0]; break
                except Exception:
                    pass
                if code: break
        
        if not code:
            print(f"  [!] No 2FA code received")
            return None
        print(f"  ✅ 2FA code: {code}")
        
        r4 = client.post(f"/v1/client/sign_ins/{sid}/attempt_second_factor", params=params,
            data={"strategy": "email_code", "code": code})
        d4 = r4.json()
        if d4.get("errors"):
            print(f"  [!] 2FA failed: {d4['errors'][0].get('message', '')[:80]}")
            return None
        
        r4resp = d4.get("response", d4)
        sessions = d4.get("client", {}).get("sessions", [])
        st = sessions[0].get("last_active_token", {}).get("jwt", "") if sessions else ""
        
        if st:
            print(f"  ✅ Login with 2FA: {email}")
            return st
        
        print(f"  [!] Login result: {r4resp.get('status') if isinstance(r4resp, dict) else 'unknown'}")
        return None


def clawdi_addon_credits(session_token):
    """Get addon credits"""
    with httpx.Client(timeout=15) as c:
        r = c.get(f"{CLAWDI_API}/subscription/addon-credits",
            headers={"Authorization": f"Bearer {session_token}"})
        return r.status_code, r.json() if r.status_code == 200 else r.text[:300]


# ============================================================
# Main Registration Flow
# ============================================================
def register_one(num=1):
    print(f"\n{'='*60}")
    print(f"  Account #{num} (ref: {REF_CODE})")
    print(f"{'='*60}")

    device_token = str(uuid.uuid4())

    # 0. Capture referral
    capture = capture_referral(REF_CODE, device_token)
    anonymous_token = capture.get("anonymous_token") if capture else None

    # 1. Email
    print(f"\n[1] Creating temp email...")
    email, yyds_token, yyds_id = yyds_create_email()
    if not email:
        return None

    # 2. Turnstile (LOCAL CloakBrowser solver)
    print(f"\n[2] Solving Turnstile (CloakBrowser solver)...")
    token = solve_turnstile()
    if not token:
        return None

    # 3+4+5+6. Register + Verify (single session!)
    print(f"\n[3] Registering + Verifying on Clerk...")
    result = clerk_full_flow(email, PASSWORD, token, yyds_token, email)
    if not result:
        return None
    uid = result["uid"]
    session_token = result["session_token"]

    # 7. Identify referral
    if session_token:
        ref_result = identify_referral(session_token, device_token, anonymous_token, REF_CODE)
    else:
        ref_result = None

    # Show subscription info
    if session_token:
        print(f"\n[8] Subscription info:")
        sub_status, sub_data = clawdi_get_subscription(session_token)
        print(f"  Current sub: {sub_status} → {json.dumps(sub_data)[:100] if isinstance(sub_data, dict) else sub_data[:100]}")

        fee_status, fee_data = clawdi_activation_fee(session_token)
        print(f"  Activation fee: {fee_status} → {json.dumps(fee_data) if isinstance(fee_data, dict) else fee_data[:100]}")

        credits_status, credits_data = clawdi_addon_credits(session_token)
        print(f"  Credits: {credits_status} → {json.dumps(credits_data) if isinstance(credits_data, dict) else credits_data[:100]}")

    result_data = {
        "email": email,
        "password": PASSWORD,
        "user_id": uid,
        "session_token": session_token,
        "ref_code": REF_CODE,
        "device_token": device_token,
        "anonymous_token": anonymous_token,
        "referral_captured": capture is not None,
        "referral_identified": ref_result is not None,
        "status": "complete",
        "registered_at": datetime.datetime.now().isoformat(),
    }

    print(f"\n{'='*60}")
    print(f"  ✅ Account #{num} done!")
    print(f"  Email:  {email}")
    print(f"  UserID: {uid}")
    print(f"  Ref:    {REF_CODE} (capture={'✅' if capture else '❌'} identify={'✅' if ref_result else '❌'})")
    print(f"{'='*60}")

    return result_data


def main():
    print("""
╔══════════════════════════════════════════════════════╗
║  Clawdi.ai Registration + Subscription API            ║
║  YYDS Mail + LOCAL CloakBrowser Solver + Clerk        ║
╚══════════════════════════════════════════════════════╝
""")

    # Check solver is running
    print("[*] Checking Turnstile solver...")
    try:
        r = httpx.get(f"{SOLVER_URL}/", timeout=5)
        print(f"  ✅ Solver running at {SOLVER_URL}")
    except Exception as e:
        print(f"  ❌ Solver not reachable at {SOLVER_URL}: {e}")
        print(f"  Start it with: cd Turnstile-Solver && python api_solver.py --thread 2 --cors all")
        return

    num = int(sys.argv[1]) if len(sys.argv) > 1 else 1

    # Show plans
    print("\n[*] Available plans:")
    plans = clawdi_get_plans()
    if plans:
        for p in plans:
            print(f"  {p['slug']:10} {p['name']:10} ${p['price_cents']/100:.2f}/mo  {p['monthly_budget_credits']} credits  {p['vcpu']}vCPU/{p['ram_gb']}GB/{p['disk_size']}GB")
    print()

    # Validate referral
    print(f"[*] Referral code: {REF_CODE}")
    try:
        with httpx.Client(timeout=15) as c:
            r = c.post(f"{CLAWDI_API}/referrals/validate",
                headers={"Content-Type": "application/json"},
                json={"code": REF_CODE})
            if r.status_code == 200:
                d = r.json()
                print(f"  ✅ Valid! Referrer: {d.get('referrer_display_name')} ({d.get('referrer_user_id')})")
            else:
                print(f"  [!] {r.status_code}")
    except Exception as e:
        print(f"  [!] Skipped: {e}")
    print()

    results = []
    failed = 0
    for i in range(1, num + 1):
        result = register_one(i)
        if result:
            results.append(result)
        else:
            failed += 1
        if i < num:
            delay = random.uniform(3, 8)
            print(f"\n[*] Waiting {delay:.1f}s...")
            time.sleep(delay)

    print(f"\n{'='*60}")
    print(f"  Results: {len(results)} success, {failed} failed")
    print(f"{'='*60}")

    with open(OUTPUT_FILE, "w") as f:
        json.dump(results, f, indent=2, ensure_ascii=False)
    print(f"[*] Saved to {OUTPUT_FILE}")

    for r in results:
        print(f"  ✅ {r['email']} / {r['password']} / uid={r['user_id']} / ref={'✅' if r['referral_identified'] else '❌'}")


if __name__ == "__main__":
    main()