#!/usr/bin/env python3
"""
X Stealth Engagement using Patchright (undetected Playwright fork)
Bypasses Error 226 automation detection for replies
"""

import asyncio
import json
import random
import sys
from datetime import datetime
from pathlib import Path

try:
    from patchright.async_api import async_playwright
except ImportError:
    print("ERROR: pip install patchright")
    sys.exit(1)

# Persistent browser profile (retains login)
USER_DATA_DIR = Path.home() / "openclaw_x_stealth_profile"
COOKIES_PATH = Path(__file__).parent / "x_cookies.json"
LOG_PATH = Path(__file__).parent.parent.parent.parent / "PROGRESS-LOG.md"

# Target accounts for engagement
TARGET_ACCOUNTS = [
    "Trulieve",
    "MUV_FL", 
    "TheFloweryFL",
    "GrowHealthyFL",
    "SunburnCannabis",
    "FLCannabis",
    "FloridaMMTC",
]


# Voice profile for LLM-generated replies
VOICE_PROFILE = """You are a Florida medical cannabis patient in your late 20s replying to dispensary tweets.

YOUR VIBE:
- Sassy, funny, a little sarcastic but not mean
- You know the FL market well - prices, strains, which dispos are good
- Self-deprecating humor about spending too much money on weed
- Use lowercase, minimal punctuation, gen-z/millennial twitter speak
- Emojis sparingly (1-2 max, or none)
- Never sound like a brand or corporate account
- Never say "transparency" or generic marketing speak
- You're a real patient who's excited about good deals and annoyed by bad ones

REPLY STYLE:
- Short (under 200 chars ideally, max 280)
- React to what they ACTUALLY said, not generic praise
- Can be a joke, observation, question, or relatable take
- Imagine your friends seeing this - would they laugh or cringe?

BAD REPLIES (never do this):
- "Great deal! Thanks for sharing!"
- "Love the transparency!"
- "FL patients appreciate this!"
- Anything a bot would say

GOOD REPLIES:
- "my wallet already crying and i haven't even left yet"
- "why would you post this when i'm trying to save money"
- "ok but last time i got there it was already gone 😭"
- "the way i just abandoned my cart at [competitor]"
"""


def generate_smart_reply(tweet_text: str, username: str) -> str:
    """Use LLM to generate a contextual, human reply."""
    import urllib.request
    import urllib.error
    
    if not tweet_text or len(tweet_text.strip()) < 10:
        return random.choice(["👀", "say less", "on my way"])
    
    full_prompt = f"""{VOICE_PROFILE}

Tweet from @{username}:
"{tweet_text}"

Write ONE reply tweet (under 150 chars). Just the reply, no quotes or labels."""

    # Use Z.ai API (Claude via Anthropic-compatible endpoint)
    try:
        payload = json.dumps({
            "model": "claude-sonnet-4-20250514",
            "max_tokens": 100,
            "messages": [
                {"role": "user", "content": full_prompt}
            ]
        }).encode()
        
        req = urllib.request.Request(
            "https://api.z.ai/api/anthropic/v1/messages",
            data=payload,
            headers={
                "Content-Type": "application/json",
                "x-api-key": "3887e2ed1a0a42ada0f9dc04461d3dce.O9dPzq1s2KQB2uMm",
                "anthropic-version": "2023-06-01"
            }
        )
        
        with urllib.request.urlopen(req, timeout=20) as resp:
            data = json.loads(resp.read().decode())
            reply = data["content"][0]["text"].strip()
            # Clean up quotes/labels
            reply = reply.strip('"\'')
            for prefix in ["reply:", "response:", "tweet:"]:
                if reply.lower().startswith(prefix):
                    reply = reply[len(prefix):].strip()
            if len(reply) <= 280 and len(reply) > 5:
                return reply
    except Exception as e:
        print(f"  LLM error: {e}", flush=True)
    
    # Fallback templates if LLM fails
    tweet_lower = tweet_text.lower()
    if any(w in tweet_lower for w in ["sale", "deal", "%", "off", "bogo"]):
        return random.choice([
            "my budget didn't need this today",
            "and there goes my paycheck 😭",
            "this is financial violence",
        ])
    elif any(w in tweet_lower for w in ["new", "drop", "fresh", "restock"]):
        return random.choice([
            "running not walking",
            "this better still be there friday",
            "say less 🏃‍♂️",
        ])
    else:
        return random.choice(["noted 📝", "👀", "interesting"])


def log_action(action: str, details: str = "") -> None:
    """Log to file and console."""
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(f"[{ts}] {action}: {details}", flush=True)
    
    try:
        content = LOG_PATH.read_text() if LOG_PATH.exists() else "# PROGRESS-LOG.md\n"
        today = datetime.now().strftime("%Y-%m-%d")
        if f"### {today}" not in content:
            content += f"\n### {today}\n| Time | Action | Details |\n|------|--------|--------|\n"
        content += f"| {ts} | {action} | {details} |\n"
        LOG_PATH.write_text(content)
    except:
        pass


def load_cookies_for_browser():
    """Load twikit cookies and convert for browser injection."""
    if not COOKIES_PATH.exists():
        return []
    
    twikit_cookies = json.loads(COOKIES_PATH.read_text())
    browser_cookies = []
    
    # Map twikit cookies to browser format
    cookie_mapping = {
        "auth_token": {"name": "auth_token", "domain": ".x.com", "path": "/", "httpOnly": True, "secure": True},
        "ct0": {"name": "ct0", "domain": ".x.com", "path": "/", "httpOnly": False, "secure": True},
        "twid": {"name": "twid", "domain": ".x.com", "path": "/", "httpOnly": False, "secure": True},
        "guest_id": {"name": "guest_id", "domain": ".x.com", "path": "/", "httpOnly": False, "secure": True},
        "personalization_id": {"name": "personalization_id", "domain": ".x.com", "path": "/", "httpOnly": False, "secure": True},
    }
    
    for key, value in twikit_cookies.items():
        if key in cookie_mapping:
            cookie = cookie_mapping[key].copy()
            cookie["value"] = value
            browser_cookies.append(cookie)
    
    return browser_cookies


async def human_type(page, selector: str, text: str):
    """Type with human-like delays and occasional pauses."""
    element = page.locator(selector)
    await element.click()
    await asyncio.sleep(random.uniform(0.3, 0.7))
    
    for char in text:
        await element.press_sequentially(char, delay=random.randint(50, 120))
        # Occasional thinking pause
        if random.random() < 0.03:
            await asyncio.sleep(random.uniform(0.5, 1.5))
    
    await asyncio.sleep(random.uniform(0.2, 0.5))


async def human_scroll(page, scrolls: int = 5):
    """Scroll like a human browsing."""
    for _ in range(scrolls):
        delta = random.randint(150, 450)
        await page.mouse.wheel(0, delta)
        await asyncio.sleep(random.uniform(0.8, 2.5))


async def run_stealth_engagement(reply_count: int = 2, like_count: int = 5):
    """Run engagement round with stealth browser."""
    
    print("🥷 Starting Stealth X Engagement (Patchright)", flush=True)
    print("=" * 50, flush=True)
    
    USER_DATA_DIR.mkdir(exist_ok=True)
    
    async with async_playwright() as p:
        # Launch with persistent profile
        context = await p.chromium.launch_persistent_context(
            str(USER_DATA_DIR),
            headless=True,
            viewport={"width": 1366, "height": 768},
            args=[
                "--no-sandbox",
                "--disable-dev-shm-usage",
            ]
        )
        
        page = context.pages[0] if context.pages else await context.new_page()
        stats = {"likes": 0, "replies": 0, "follows": 0}
        
        try:
            # Inject cookies from twikit
            browser_cookies = load_cookies_for_browser()
            if browser_cookies:
                print(f"🍪 Injecting {len(browser_cookies)} cookies...", flush=True)
                await context.add_cookies(browser_cookies)
            
            # Go to X home
            print("📱 Loading X...", flush=True)
            await page.goto("https://x.com/home", wait_until="domcontentloaded")
            await asyncio.sleep(4)
            
            # Check if logged in
            current_url = page.url
            if "login" in current_url.lower():
                print("⚠️ Not logged in. Cookies may be expired.", flush=True)
                log_action("AUTH_REQUIRED", "Cookies invalid or expired")
                await context.close()
                return stats
            
            # Dismiss any overlays/modals
            try:
                close_btns = await page.locator('[aria-label="Close"]').all()
                for btn in close_btns[:2]:
                    if await btn.is_visible():
                        await btn.click()
                        await asyncio.sleep(0.5)
            except:
                pass
            
            # Dismiss cookie consent if present
            try:
                consent = page.locator('[data-testid="twc-cc-mask"]')
                if await consent.is_visible():
                    await page.keyboard.press("Escape")
                    await asyncio.sleep(1)
            except:
                pass
            
            log_action("SESSION", "Logged in via cookies, starting engagement")
            
            # Warm up - scroll home feed
            print("🔥 Warming up session...", flush=True)
            await human_scroll(page, 4)
            await asyncio.sleep(2)
            
            # Engage with target accounts
            targets = random.sample(TARGET_ACCOUNTS, min(3, len(TARGET_ACCOUNTS)))
            
            for username in targets:
                print(f"\n👤 Engaging with @{username}...", flush=True)
                
                try:
                    await page.goto(f"https://x.com/{username}", wait_until="domcontentloaded")
                    await asyncio.sleep(random.uniform(2, 4))
                    
                    # Scroll their profile
                    await human_scroll(page, 2)
                    
                    # Find tweets to engage with
                    tweets = await page.locator('[data-testid="tweet"]').all()
                    
                    for i, tweet in enumerate(tweets[:3]):
                        if stats["likes"] >= like_count and stats["replies"] >= reply_count:
                            break
                        
                        try:
                            # Dismiss any modal overlays first (X loves these)
                            try:
                                mask = page.locator('[data-testid="mask"]')
                                if await mask.is_visible():
                                    await page.keyboard.press("Escape")
                                    await asyncio.sleep(0.5)
                                # Also try clicking outside layers
                                layers = page.locator('#layers')
                                if await layers.locator('[role="listitem"]').count() > 0:
                                    await page.keyboard.press("Escape")
                                    await asyncio.sleep(0.5)
                            except:
                                pass
                            
                            # Like the tweet
                            if stats["likes"] < like_count:
                                like_btn = tweet.locator('[data-testid="like"]')
                                if await like_btn.is_visible():
                                    await like_btn.click(timeout=5000)
                                    stats["likes"] += 1
                                    log_action("LIKE", f"@{username}")
                                    await asyncio.sleep(random.uniform(1, 3))
                            
                            # Reply to tweet (the key feature)
                            if stats["replies"] < reply_count and i == 0:
                                # First get the tweet text to reply intelligently
                                tweet_text = ""
                                try:
                                    text_elem = tweet.locator('[data-testid="tweetText"]')
                                    if await text_elem.count() > 0:
                                        tweet_text = (await text_elem.inner_text()).lower()
                                except:
                                    pass
                                
                                reply_btn = tweet.locator('[data-testid="reply"]')
                                if await reply_btn.is_visible():
                                    await reply_btn.click()
                                    await asyncio.sleep(random.uniform(1.5, 3))
                                    
                                    # Generate contextual reply based on tweet content
                                    reply_text = generate_smart_reply(tweet_text, username)
                                    
                                    textarea = page.locator('[data-testid="tweetTextarea_0"]')
                                    if await textarea.is_visible():
                                        await human_type(page, '[data-testid="tweetTextarea_0"]', reply_text)
                                        await asyncio.sleep(random.uniform(1, 2))
                                        
                                        # Submit reply
                                        submit_btn = page.locator('[data-testid="tweetButton"]')
                                        if await submit_btn.is_enabled():
                                            await submit_btn.click()
                                            await asyncio.sleep(2)
                                            
                                            # Verify reply dialog closed (success indicator)
                                            dialog_still_open = await textarea.is_visible()
                                            if dialog_still_open:
                                                # Failed - take screenshot
                                                ss_path = Path(__file__).parent / f"reply_fail_{username}_{datetime.now().strftime('%H%M%S')}.png"
                                                await page.screenshot(path=str(ss_path))
                                                print(f"  ⚠️ Reply dialog still open - FAILED. Screenshot: {ss_path.name}", flush=True)
                                                await page.keyboard.press("Escape")
                                            else:
                                                stats["replies"] += 1
                                                log_action("REPLY", f"@{username}: {reply_text[:40]}...")
                                                print(f"  ✅ Reply posted: {reply_text[:50]}...", flush=True)
                                            await asyncio.sleep(random.uniform(2, 4))
                                        else:
                                            print(f"  ⚠️ Submit button not enabled", flush=True)
                                            ss_path = Path(__file__).parent / f"submit_disabled_{username}_{datetime.now().strftime('%H%M%S')}.png"
                                            await page.screenshot(path=str(ss_path))
                                            await page.keyboard.press("Escape")
                                    else:
                                        print(f"  ⚠️ Textarea not visible", flush=True)
                                        await page.keyboard.press("Escape")
                        
                        except Exception as e:
                            print(f"  Tweet error: {e}", flush=True)
                            continue
                    
                    await asyncio.sleep(random.uniform(2, 5))
                    
                except Exception as e:
                    print(f"  Error with @{username}: {e}", flush=True)
                    continue
            
            # Summary
            print("\n" + "=" * 50, flush=True)
            print("📊 STEALTH ENGAGEMENT COMPLETE", flush=True)
            print(f"   Likes: {stats['likes']}", flush=True)
            print(f"   Replies: {stats['replies']}", flush=True)
            print("=" * 50, flush=True)
            
            log_action("STEALTH_DONE", f"L:{stats['likes']} R:{stats['replies']}")
            
        except Exception as e:
            print(f"Error: {e}", flush=True)
            await page.screenshot(path=f"error_stealth_{datetime.now().strftime('%H%M%S')}.png")
        finally:
            await context.close()
    
    return stats


async def login_interactive():
    """Open browser for manual login."""
    print("🔐 Opening browser for manual X login...", flush=True)
    USER_DATA_DIR.mkdir(exist_ok=True)
    
    async with async_playwright() as p:
        context = await p.chromium.launch_persistent_context(
            str(USER_DATA_DIR),
            headless=False,  # Visible for login
            viewport={"width": 1366, "height": 768},
        )
        page = context.pages[0] if context.pages else await context.new_page()
        await page.goto("https://x.com/login")
        
        print("Please log in manually. Press Enter when done...")
        input()
        
        await context.close()
        print("✅ Login saved to profile")


async def main():
    if "--login" in sys.argv:
        await login_interactive()
        return
    
    reply_count = 2
    like_count = 5
    
    if "--heavy" in sys.argv:
        reply_count, like_count = 4, 10
    if "--light" in sys.argv:
        reply_count, like_count = 1, 3
    if "--dry-run" in sys.argv:
        print("DRY RUN - would engage:", flush=True)
        print(f"  Replies: {reply_count}", flush=True)
        print(f"  Likes: {like_count}", flush=True)
        return
    
    await run_stealth_engagement(reply_count, like_count)


if __name__ == "__main__":
    asyncio.run(main())
