import ssl
import urllib.request

ssl._create_default_https_context = ssl._create_unverified_context

import os
import json
import asyncio
import logging
from fake_useragent import UserAgent
import subprocess
import platform
import time
from datetime import datetime, timedelta, date
from typing import Dict, Set, Optional, List, Tuple
import random
import requests
from collections import defaultdict
import urllib.parse
from dataclasses import dataclass
import threading
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters, ConversationHandler
from concurrent.futures import ThreadPoolExecutor
from telegram import Update, BotCommand
from telegram.ext import Application, CommandHandler, ContextTypes
from telegram.constants import ParseMode
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import warnings

warnings.filterwarnings('ignore')

from camoufox.sync_api import Camoufox
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

# === NEW IMPORTS ===
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError

# Auto-detect stealth library version
# Kompatibel dengan:
#   - tf-playwright-stealth (stealth_sync function)
#   - playwright-stealth v1.x (stealth_sync function)  
#   - playwright-stealth v2.x (Stealth class)
_stealth_mode = None
_stealth_obj = None

try:
    from playwright_stealth import stealth_sync
    _stealth_mode = "function"
    print("✅ Stealth loaded: stealth_sync (tf-playwright-stealth / v1.x)")
except ImportError:
    try:
        from playwright_stealth import Stealth
        _stealth_obj = Stealth()
        _stealth_mode = "class_v2"
        print("✅ Stealth loaded: Stealth class (playwright-stealth v2.x)")
    except ImportError:
        _stealth_mode = "manual"
        print("⚠️ No stealth library found - using manual anti-detection")


def _apply_stealth_scripts(page):
    """
    Manual stealth - tidak butuh library external.
    Inject anti-detection scripts langsung ke page.
    """
    page.add_init_script("""
        // === HIDE WEBDRIVER ===
        Object.defineProperty(navigator, 'webdriver', {get: () => undefined});

        // === REMOVE AUTOMATION FLAGS ===
        delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
        delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
        delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
        delete window.__playwright;
        delete window.__pw_manual;
        delete window.__PW_inspect;

        // === FAKE PLUGINS ===
        Object.defineProperty(navigator, 'plugins', {
            get: () => {
                const p = [
                    {name:'Chrome PDF Plugin',filename:'internal-pdf-viewer',description:'Portable Document Format',length:1},
                    {name:'Chrome PDF Viewer',filename:'mhjfbmdgcfjbbpaeojofohoefgiehjai',description:'',length:1},
                    {name:'Native Client',filename:'internal-nacl-plugin',description:'',length:2}
                ];
                p.length = 3;
                p.item = (i) => p[i];
                p.namedItem = (n) => p.find(x => x.name === n);
                p.refresh = () => {};
                return p;
            },
        });

        // === LANGUAGES ===
        Object.defineProperty(navigator, 'languages', {
            get: () => ['id-ID', 'id', 'en-US', 'en'],
        });

        // === HARDWARE ===
        Object.defineProperty(navigator, 'hardwareConcurrency', {get: () => 4});
        Object.defineProperty(navigator, 'deviceMemory', {get: () => 8});
        Object.defineProperty(navigator, 'maxTouchPoints', {get: () => 0});

        // === CHROME RUNTIME (looks like real Chrome) ===
        window.chrome = {
            runtime: {
                connect: function(){},
                sendMessage: function(){},
            },
            loadTimes: function(){ return {}; },
            csi: function(){ return {}; },
        };

        // === PERMISSIONS ===
        const origQuery = window.navigator.permissions.query;
        window.navigator.permissions.query = (parameters) => (
            parameters.name === 'notifications' ?
            Promise.resolve({state: Notification.permission}) :
            origQuery(parameters)
        );

        // === IFRAME CONTENTWINDOW ===
        try {
            const origGetter = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
            Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
                get: function() {
                    const v = origGetter.get.call(this);
                    if (v && v.self !== v.top) {
                        try { Object.defineProperty(v, 'chrome', {value: window.chrome}); } catch(e) {}
                    }
                    return v;
                }
            });
        } catch(e) {}

        // === MOUSE POSITION TRACKER (for reCAPTCHA score) ===
        window._mouseX = 0;
        window._mouseY = 0;
        document.addEventListener('mousemove', (e) => {
            window._mouseX = e.clientX;
            window._mouseY = e.clientY;
        }, {passive: true});

        // ============================================
        // === CANVAS FINGERPRINT - CONSISTENT PER SESSION ===
        // ============================================
        (function() {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const txt = 'fingerprint,🎨';
            ctx.textBaseline = 'top';
            ctx.font = '14px Arial';
            ctx.textBaseline = 'alphabetic';
            ctx.fillStyle = '#f60';
            ctx.fillRect(125,1,62,20);
            ctx.fillStyle = '#069';
            ctx.fillText(txt, 2, 15);
            ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
            ctx.fillText(txt, 4, 17);
            
            const origData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const noiseSeed = Math.random() * 100;
            
            // Override toDataURL dengan consistent noise
            const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
            HTMLCanvasElement.prototype.toDataURL = function(type) {
                if (this.width > 16 && this.height > 16) {
                    const ctx = this.getContext('2d');
                    if (ctx) {
                        const imgData = ctx.getImageData(0, 0, Math.min(64, this.width), Math.min(64, this.height));
                        for (let i = 0; i < imgData.data.length; i += 100) {
                            imgData.data[i] ^= Math.floor(noiseSeed + i) % 3;
                        }
                        ctx.putImageData(imgData, 0, 0);
                    }
                }
                return origToDataURL.apply(this, arguments);
            };
            
            // Override getImageData
            const origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
            CanvasRenderingContext2D.prototype.getImageData = function() {
                const imgData = origGetImageData.apply(this, arguments);
                for (let i = 0; i < imgData.data.length; i += 100) {
                    imgData.data[i] ^= Math.floor(noiseSeed + i) % 3;
                }
                return imgData;
            };
        })();

        // === WEBGL VENDOR ===
        try {
            const getParam = WebGLRenderingContext.prototype.getParameter;
            WebGLRenderingContext.prototype.getParameter = function(p) {
                if (p === 37445) return 'Google Inc. (NVIDIA)';
                if (p === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0, D3D11)';
                return getParam.call(this, p);
            };
        } catch(e) {}
        
        // === WEBGL2 VENDOR ===
        try {
            const getParam2 = WebGL2RenderingContext.prototype.getParameter;
            WebGL2RenderingContext.prototype.getParameter = function(p) {
                if (p === 37445) return 'Google Inc. (NVIDIA)';
                if (p === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0, D3D11)';
                return getParam2.call(this, p);
            };
        } catch(e) {}
    """)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
ua = {'User-Agent': UserAgent().random}
PROXY_LIST_URL = "https://raw.githubusercontent.com/ZeusFtrOfc/BotZeus/refs/heads/main/proxy_rotating.txt"
SUCCESS_IMAGE_PATH = "success.jpg"
WAITING_CONTENT = 1
WAITING_KEYWORD = 2
WAITING_SCHEDULE = 3
WAITING_SCHEDULE_CONTENT = 4
WAITING_SCHEDULE_KEYWORD = 5

def cleanup_orphaned_extensions():
    try:
        current_dir = os.getcwd()
        cleaned_count = 0
        for filename in os.listdir(current_dir):
            if filename.startswith("proxy_auth_plugin_") and filename.endswith(".zip"):
                try:
                    file_path = os.path.join(current_dir, filename)
                    os.remove(file_path)
                    cleaned_count += 1
                    print(f"🧹 Cleaned up orphaned extension: {filename}")
                except Exception as e:
                    print(f"⚠️ Could not cleanup {filename}: {e}")
        if cleaned_count > 0:
            print(f"✅ Cleaned up {cleaned_count} orphaned extension file(s)")
        return cleaned_count
    except Exception as e:
        print(f"⚠️ Orphaned file cleanup error: {e}")
        return 0

def detect_proxy_protocol(proxy_string: str) -> str:
    proxy_lower = proxy_string.lower()
    if proxy_lower.startswith("socks5://"):
        return "socks5"
    elif proxy_lower.startswith("socks4://"):
        return "socks4"
    elif proxy_lower.startswith("https://"):
        return "https"
    elif proxy_lower.startswith("http://"):
        return "http"
    if "socks5" in proxy_lower:
        return "socks5"
    elif "socks4" in proxy_lower:
        return "socks4"
    elif "socks" in proxy_lower:
        return "socks5"
    return "http"

def parse_proxy_string(proxy_string: str) -> Dict:
    proxy_string = proxy_string.strip()
    protocol = detect_proxy_protocol(proxy_string)
    clean_string = (
        proxy_string.replace("http://", "")
        .replace("https://", "")
        .replace("socks4://", "")
        .replace("socks5://", "")
    )
    if "@" in clean_string:
        auth_part, server_part = clean_string.split("@", 1)
        if ":" in auth_part and ":" in server_part:
            user, password = auth_part.split(":", 1)
            host, port = server_part.split(":", 1)
            return {
                "host": host,
                "port": port,
                "user": user,
                "password": password,
                "protocol": protocol,
                "region": "Unknown",
            }
    parts = clean_string.split(":")
    if len(parts) == 4:
        return {
            "host": parts[0],
            "port": parts[1],
            "user": parts[2],
            "password": parts[3],
            "protocol": protocol,
            "region": "Unknown",
        }
    elif len(parts) == 2:
        return {
            "host": parts[0],
            "port": parts[1],
            "user": "",
            "password": "",
            "protocol": protocol,
            "region": "Unknown",
        }
    return None

def fetch_proxy_list_from_url(url: str) -> List[Dict]:
    try:
        print(f"📡 Fetching proxy list from: {url}")
        response = requests.get(url, timeout=15)
        response.raise_for_status()
        raw_content = response.text
        if not raw_content or not raw_content.strip():
            print("❌ Empty response from proxy URL")
            return []
        proxy_lines = [line.strip() for line in raw_content.split("\n") if line.strip()]
        if not proxy_lines:
            print("❌ No valid proxy lines found")
            return []
        proxy_list = []
        for line in proxy_lines:
            proxy = parse_proxy_string(line)
            if proxy:
                proxy_list.append(proxy)
                print(f"  ✓ Loaded: {proxy['protocol'].upper()} {proxy['host']}:{proxy['port']}")
        if not proxy_list:
            print("❌ No valid proxies parsed")
            return []
        print(f"✅ Successfully loaded {len(proxy_list)} proxies")
        protocol_count = {}
        for p in proxy_list:
            protocol = p["protocol"]
            protocol_count[protocol] = protocol_count.get(protocol, 0) + 1
        print("\n📊 Proxy Protocol Summary:")
        for protocol, count in protocol_count.items():
            print(f"   {protocol.upper()}: {count} proxies")
        return proxy_list
    except requests.exceptions.Timeout:
        print(f"❌ Timeout fetching proxy list (15s exceeded)")
        return []
    except requests.exceptions.RequestException as e:
        print(f"❌ Network error fetching proxy list: {e}")
        return []
    except Exception as e:
        print(f"❌ Failed to fetch proxy list: {e}")

        import traceback

        traceback.print_exc()
        return []
PROXY_LIST = fetch_proxy_list_from_url(PROXY_LIST_URL)
if not PROXY_LIST:
    print("⚠️ No proxies loaded! Bot will run without proxies.")

class ProxyManager:
    def __init__(self, proxy_list: List[Dict]):
        self.proxy_list = proxy_list
        self.current_index = 0
        self.usage_count = defaultdict(int)
        self.last_used = {}
        self.failed_proxies = set()
        self.proxy_scores = defaultdict(int)
        self.lock = threading.Lock()
        self.proxy_url = PROXY_LIST_URL
        self.last_update = datetime.now()
        self.update_interval_minutes = 15
        self.success_count = defaultdict(int)
        self.failure_count = defaultdict(int)
        self.last_success_time = {}

    def get_best_proxy(self) -> Dict:
        with self.lock:
            if not self.proxy_list:
                raise Exception("No proxies available!")
            available = [
                p for p in self.proxy_list
                if f"{p['protocol']}://{p['host']}:{p['port']}" not in self.failed_proxies
            ]
            if not available:
                print("⚠️ All proxies failed, resetting...")
                self.failed_proxies.clear()
                available = self.proxy_list

            def get_success_rate(proxy):
                key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
                successes = self.success_count.get(key, 0)
                failures = self.failure_count.get(key, 0)
                total = successes + failures
                if total == 0:
                    return 0.5  # Unknown proxy, give neutral score
                return successes / total
            sorted_proxies = sorted(available, key=get_success_rate, reverse=True)
            top_proxies = sorted_proxies[:3] if len(sorted_proxies) >= 3 else sorted_proxies
            proxy = random.choice(top_proxies)
            proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
            self.usage_count[proxy_key] += 1
            self.last_used[proxy_key] = datetime.now()
            success_rate = get_success_rate(proxy) * 100
            print(f"📊 Selected proxy (success rate: {success_rate:.0f}%): {proxy['host']}:{proxy['port']}")
            return proxy

    def mark_proxy_success(self, proxy: Dict):
        with self.lock:
            proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
            if proxy_key in self.failed_proxies:
                self.failed_proxies.discard(proxy_key)
            self.proxy_scores[proxy_key] += 1
            self.success_count[proxy_key] += 1
            self.last_success_time[proxy_key] = datetime.now()
            print(f"✅ Proxy success recorded: {proxy_key}")

    def mark_proxy_failed(self, proxy: Dict):
        with self.lock:
            proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
            self.proxy_scores[proxy_key] -= 1
            self.failure_count[proxy_key] += 1
            successes = self.success_count.get(proxy_key, 0)
            failures = self.failure_count.get(proxy_key, 0)
            if failures >= 3 and (successes == 0 or failures / (successes + failures) > 0.7):
                self.failed_proxies.add(proxy_key)
                print(f"❌ Proxy marked as failed: {proxy_key}")
            else:
                print(f"⚠️ Proxy failure recorded: {proxy_key} ({failures} failures)")
        self.update_interval_minutes = 15

    def reload_proxies_from_url(self) -> Tuple[bool, str, int]:
        try:
            new_proxy_list = fetch_proxy_list_from_url(self.proxy_url)
            if not new_proxy_list:
                return False, "Failed to fetch proxy list from URL (empty or invalid)", len(self.proxy_list)
            if len(new_proxy_list) < 1:
                return False, "New proxy list has insufficient proxies", len(self.proxy_list)
            with self.lock:
                old_count = len(self.proxy_list)
                old_keys = {f"{p['protocol']}://{p['host']}:{p['port']}" for p in self.proxy_list}
                new_keys = {f"{p['protocol']}://{p['host']}:{p['port']}" for p in new_proxy_list}
                added = new_keys - old_keys
                removed = old_keys - new_keys
                self.proxy_list = new_proxy_list
                self.last_update = datetime.now()
                self.failed_proxies = {p for p in self.failed_proxies if p in new_keys}
                for removed_key in removed:
                    if removed_key in self.usage_count:
                        del self.usage_count[removed_key]
                    if removed_key in self.last_used:
                        del self.last_used[removed_key]
                    if removed_key in self.proxy_scores:
                        del self.proxy_scores[removed_key]
                new_count = len(self.proxy_list)
                if added:
                    print(f"\n✅ Added Proxies:")
                    for proxy_key in list(added)[:5]:
                        print(f"  + {proxy_key}")
                    if len(added) > 5:
                        print(f"  ... and {len(added) - 5} more")
                if removed:
                    print(f"\n❌ Removed Proxies:")
                    for proxy_key in list(removed)[:5]:
                        print(f"  - {proxy_key}")
                    if len(removed) > 5:
                        print(f"  ... and {len(removed) - 5} more")
                print("\n" + "="*80)
                message = f"Updated: {old_count} → {new_count} proxies"
                if added:
                    message += f" (+{len(added)} new)"
                if removed:
                    message += f" (-{len(removed)} removed)"
                return True, message, new_count
        except Exception as e:
            error_msg = f"Reload failed: {e}"
            print(f"❌ {error_msg}")

            import traceback

            traceback.print_exc()
            return False, error_msg, len(self.proxy_list)

    def should_auto_update(self) -> bool:
        elapsed = datetime.now() - self.last_update
        return elapsed.total_seconds() >= (self.update_interval_minutes * 60)

    def get_last_update_info(self) -> str:
        elapsed = datetime.now() - self.last_update
        minutes = int(elapsed.total_seconds() / 60)
        if minutes < 1:
            return "Just now"
        elif minutes < 60:
            return f"{minutes} minute(s) ago"
        else:
            hours = minutes // 60
            return f"{hours} hour(s) ago"

    def get_next_proxy(self) -> Dict:
        with self.lock:
            if not self.proxy_list:
                raise Exception("No proxies available!")
            attempts = 0
            max_attempts = len(self.proxy_list) * 2
            while attempts < max_attempts:
                proxy = self.proxy_list[self.current_index]
                self.current_index = (self.current_index + 1) % len(self.proxy_list)
                proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
                if proxy_key not in self.failed_proxies:
                    self.usage_count[proxy_key] += 1
                    self.last_used[proxy_key] = datetime.now()
                    return proxy
                attempts += 1
            print("⚠️ All proxies marked as failed, resetting and retrying...")
            self.failed_proxies.clear()
            proxy = self.proxy_list[0]
            proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
            self.usage_count[proxy_key] += 1
            self.last_used[proxy_key] = datetime.now()
            return proxy

    def get_random_proxy(self) -> Dict:
        with self.lock:
            if not self.proxy_list:
                raise Exception("No proxies available!")
            available_proxies = [
                p for p in self.proxy_list
                if f"{p['protocol']}://{p['host']}:{p['port']}" not in self.failed_proxies
            ]
            if not available_proxies:
                print("⚠️ All proxies marked as failed, resetting...")
                self.failed_proxies.clear()
                available_proxies = self.proxy_list
            proxy = random.choice(available_proxies)
            proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
            self.usage_count[proxy_key] += 1
            self.last_used[proxy_key] = datetime.now()
            return proxy

    def get_proxy_info(self, proxy: Dict) -> str:
        protocol = proxy.get("protocol", "http").upper()
        return f"{protocol} {proxy['host']}:{proxy['port']}"

    def get_stats(self) -> str:
        with self.lock:
            stats = "📊 PROXY USAGE STATISTICS:\n\n"
            for proxy in self.proxy_list:
                proxy_key = f"{proxy['protocol']}://{proxy['host']}:{proxy['port']}"
                count = self.usage_count.get(proxy_key, 0)
                score = self.proxy_scores.get(proxy_key, 0)
                last_use = self.last_used.get(proxy_key, None)
                is_failed = proxy_key in self.failed_proxies
                last_use_str = last_use.strftime("%H:%M:%S") if last_use else "Never"
                status = "❌ FAILED" if is_failed else "✅ ACTIVE"
                protocol = proxy.get("protocol", "http").upper()
                stats += f"🔹 {protocol} {proxy['host']}:{proxy['port']}\n"
                stats += f"   Status: {status}\n"
                stats += f"   Uses: {count} | Score: {score:+d} | Last: {last_use_str}\n\n"
            return stats

    def get_total_proxies(self) -> int:
        return len(self.proxy_list)
proxy_manager = ProxyManager(PROXY_LIST)

class Config:
    def __init__(self):
        self.config_file = "config.json"
        self.config = self.load_config()

    def load_config(self):
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, "r") as f:
                    return json.load(f)
            else:
                default = {
                    "bot_token": "",
                    "owner_id": "",
                    "ai_api_url": "http://157.20.32.130:7006",
                    "daily_report_limit": 3,
                    "re_report_cooldown_minutes": 20,
                    "proxy_rotation_mode": "random",
                    "max_concurrent_reports": 10,
                    "max_proxies_to_try": 4,
                    "max_submit_retries_per_proxy": 3,
                    "max_schedules_per_user": 5,
                    "min_schedule_interval_minutes": 20,
                    "success_image_path": "success.jpg",
                    "setup": {
                        "get_token": "Message @BotFather on Telegram",
                        "get_userid": "Message @userinfobot on Telegram",
                        "ai_api": "Start the AI chat server on localhost:3000"
                    }
                }
                with open(self.config_file, "w") as f:
                    json.dump(default, f, indent=4)
                return default
        except Exception as e:
            logger.error(f"Config error: {e}")
            return {}

    def get_max_schedules_per_user(self):
        return self.config.get("max_schedules_per_user", 5)

    def get_token(self):
        return self.config.get("bot_token", "")

    def get_owner_id(self):
        try:
            return int(self.config.get("owner_id", ""))
        except:
            return None

    def get_ai_api_url(self):
        return self.config.get("ai_api_url", "http://157.20.32.130:7006")

    def get_daily_limit(self):
        return self.config.get("daily_report_limit", 3)

    def get_re_report_cooldown(self):
        return self.config.get("re_report_cooldown_minutes", 20)

    def get_proxy_mode(self):
        return self.config.get("proxy_rotation_mode", "random")

    def get_max_concurrent_reports(self):
        return self.config.get("max_concurrent_reports", 10)

    def get_max_proxies_to_try(self):
        return self.config.get("max_proxies_to_try", 5)

    def get_max_submit_retries_per_proxy(self):
        return self.config.get("max_submit_retries_per_proxy", 2)

    def get_min_schedule_interval(self):
        return self.config.get("min_schedule_interval_minutes", 20)  # UBAH: return minutes

    def get_success_image_path(self):
        return self.config.get("success_image_path", SUCCESS_IMAGE_PATH)

    def is_configured(self):
        return bool(self.get_token() and self.get_owner_id())

class ProxyAutoUpdater:
    def __init__(self, proxy_manager: ProxyManager):
        self.proxy_manager = proxy_manager
        self.running = False
        self.task = None

    async def run_auto_updater(self):
        self.running = True
        consecutive_failures = 0
        max_consecutive_failures = 3
        while self.running:
            try:
                await asyncio.sleep(60)
                if not self.running:
                    break
                if self.proxy_manager.should_auto_update():
                    print(f"\n⏰ Auto-update triggered ({self.proxy_manager.update_interval_minutes} minutes elapsed)")
                    try:
                        success, message, count = await asyncio.wait_for(
                            asyncio.to_thread(self.proxy_manager.reload_proxies_from_url),
                            timeout=30.0
                        )
                        if success:
                            print(f"✅ Auto-update successful: {message}")
                            consecutive_failures = 0
                        else:
                            print(f"❌ Auto-update failed: {message}")
                            consecutive_failures += 1
                    except asyncio.TimeoutError:
                        print(f"❌ Auto-update TIMEOUT (exceeded 30s)")
                        consecutive_failures += 1
                        self.proxy_manager.last_update = datetime.now()
                    except Exception as e:
                        print(f"❌ Auto-update exception: {e}")

                        import traceback

                        traceback.print_exc()
                        consecutive_failures += 1
                        self.proxy_manager.last_update = datetime.now()
                    if consecutive_failures >= max_consecutive_failures:
                        print(f"⚠️ WARNING: {consecutive_failures} consecutive auto-update failures!")
                        print(f"⚠️ Will continue with existing {len(self.proxy_manager.proxy_list)} proxies")
                        consecutive_failures = 0
            except asyncio.CancelledError:
                break
            except Exception as e:
                print(f"❌ Auto-updater loop error: {e}")

                import traceback

                traceback.print_exc()
                await asyncio.sleep(60)
                continue
        print("🛑 Proxy auto-updater stopped")

    def start(self):
        if not self.running and not self.task:
            loop = asyncio.get_event_loop()
            self.task = loop.create_task(self.run_auto_updater())
            print("✅ Proxy auto-updater started")
            return True
        return False

    def stop(self):
        self.running = False
        if self.task:
            self.task.cancel()
            self.task = None
            print("🛑 Proxy auto-updater stopped")

class UserAccess:
    def __init__(self, user_id: int, expires_at: datetime = None):
        self.user_id = user_id
        self.created_at = datetime.now()
        self.expires_at = expires_at
        self.is_permanent = expires_at is None

    def is_expired(self) -> bool:
        if self.is_permanent:
            return False
        return datetime.now() > self.expires_at

    def get_remaining_time(self) -> str:
        if self.is_permanent:
            return "♾️ Permanent Access"
        if self.is_expired():
            return "⛔ Access Expired"
        remaining = self.expires_at - datetime.now()
        if remaining.days > 0:
            return f"⏳ {remaining.days}d {remaining.seconds//3600}h remaining"
        elif remaining.seconds > 3600:
            return f"⏳ {remaining.seconds//3600}h {(remaining.seconds%3600)//60}m remaining"
        else:
            return f"⏳ {remaining.seconds//60}m remaining"

    def to_dict(self):
        return {
            "user_id": self.user_id,
            "created_at": self.created_at.isoformat(),
            "expires_at": self.expires_at.isoformat() if self.expires_at else None,
            "is_permanent": self.is_permanent
        }

    @classmethod
    def from_dict(cls, data):
        access = cls(
            data["user_id"],
            datetime.fromisoformat(data["expires_at"]) if data["expires_at"] else None
        )
        access.created_at = datetime.fromisoformat(data["created_at"])
        return access

class ReportTracker:
    def __init__(self, daily_limit: int = 3, re_report_cooldown_minutes: int = 5):
        self.daily_limit = daily_limit
        self.re_report_cooldown_minutes = re_report_cooldown_minutes
        self.user_unique_urls: Dict[int, Dict[str, Set[str]]] = defaultdict(lambda: defaultdict(set))
        self.url_last_report: Dict[int, Dict[str, datetime]] = defaultdict(dict)
        self.url_first_report: Dict[int, Dict[str, str]] = defaultdict(dict)
        self.all_reported_urls: Set[str] = set()
        self.lock = threading.Lock()
        self.data_file = "report_tracking.json"
        self.load_data()

    def normalize_url(self, url: str) -> str:
        url = url.strip().lower()
        url = url.replace("http://", "").replace("https://", "")
        if url.startswith("www."):
            url = url[4:]
        url = url.rstrip("/")
        try:
            parsed = urllib.parse.urlparse("http://" + url)
            normalized = parsed.netloc + parsed.path
            if parsed.query:
                normalized += "?" + parsed.query
            return normalized
        except:
            return url

    def can_report(self, user_id: int, url: str) -> tuple[bool, str]:
        with self.lock:
            normalized_url = self.normalize_url(url)
            today = date.today().isoformat()
            user_unique_today = self.user_unique_urls[user_id].get(today, set())
            is_new_url_today = normalized_url not in user_unique_today
            if not is_new_url_today:
                last_report_time = self.url_last_report[user_id].get(normalized_url)
                if last_report_time:
                    time_since_last = datetime.now() - last_report_time
                    cooldown_delta = timedelta(minutes=self.re_report_cooldown_minutes)
                    if time_since_last < cooldown_delta:
                        remaining = cooldown_delta - time_since_last
                        remaining_minutes = int(remaining.total_seconds() / 60)
                        return (
                            False,
                            f"⏳ Cooldown active. Wait {remaining_minutes} minutes before re-reporting this URL"
                        )
                    else:
                        return True, "OK - RE-REPORT (reported earlier today)"
            else:
                if len(user_unique_today) >= self.daily_limit:
                    return (
                        False,
                        f"⛔ Daily limit reached ({self.daily_limit} unique URLs/day)\n⏰ Try again tomorrow"
                    )
                else:
                    first_report_date = self.url_first_report[user_id].get(normalized_url)
                    if first_report_date and first_report_date != today:
                        return True, f"OK - NEW URL TODAY (previously reported on {first_report_date})"
                    else:
                        return True, "OK - FIRST TIME REPORTING THIS URL"
            return True, "OK"

    def add_report(self, user_id: int, url: str) -> bool:
        with self.lock:
            normalized_url = self.normalize_url(url)
            today = date.today().isoformat()
            user_unique_today = self.user_unique_urls[user_id].get(today, set())
            is_new_url_today = normalized_url not in user_unique_today
            if is_new_url_today:
                if today not in self.user_unique_urls[user_id]:
                    self.user_unique_urls[user_id][today] = set()
                self.user_unique_urls[user_id][today].add(normalized_url)
                if normalized_url not in self.url_first_report[user_id]:
                    self.url_first_report[user_id][normalized_url] = today
                    print(f"📝 FIRST TIME EVER: {normalized_url} (counted in today's limit)")
                else:
                    first_date = self.url_first_report[user_id][normalized_url]
                    print(f"📝 NEW TODAY: {normalized_url} (previously reported on {first_date}, counted in today's limit)")
            else:
                first_date = self.url_first_report[user_id].get(normalized_url, today)
                print(f"🔄 RE-REPORT TODAY: {normalized_url} (first reported on {first_date}, NOT counted in today's limit)")
            self.url_last_report[user_id][normalized_url] = datetime.now()
            self.all_reported_urls.add(normalized_url)
            self.clean_old_data()
            self.save_data()
            return True

    def get_user_stats(self, user_id: int) -> Dict:
        with self.lock:
            today = date.today().isoformat()
            unique_urls_today = self.user_unique_urls[user_id].get(today, set())
            today_count = len(unique_urls_today)
            remaining = max(0, self.daily_limit - today_count)
            total_unique = len(self.url_first_report[user_id])
            return {
                "unique_urls_today": today_count,
                "remaining_today": remaining,
                "daily_limit": self.daily_limit,
                "total_unique_urls": total_unique,
                "cooldown_minutes": self.re_report_cooldown_minutes,
                "note": "Daily limit only for NEW URLs. Re-reporting only requires cooldown."
            }

    def get_url_status(self, user_id: int, url: str) -> Dict:
        with self.lock:
            normalized_url = self.normalize_url(url)
            today = date.today().isoformat()
            user_unique_today = self.user_unique_urls[user_id].get(today, set())
            reported_today = normalized_url in user_unique_today
            last_report = self.url_last_report[user_id].get(normalized_url)
            status = {
                "url": url,
                "reported_today": reported_today,
                "last_report": last_report.isoformat() if last_report else None,
                "can_report": False,
                "reason": ""
            }
            can_report, reason = self.can_report(user_id, url)
            status["can_report"] = can_report
            status["reason"] = reason
            return status

    def clean_old_data(self):
        cutoff_date = (date.today() - timedelta(days=7)).isoformat()
        for user_id in list(self.user_unique_urls.keys()):
            dates_to_remove = [
                d for d in self.user_unique_urls[user_id].keys()
                if d < cutoff_date
            ]
            for d in dates_to_remove:
                del self.user_unique_urls[user_id][d]
            if not self.user_unique_urls[user_id]:
                del self.user_unique_urls[user_id]

    def save_data(self):
        try:
            user_unique_dict = {}
            for user_id, date_urls in self.user_unique_urls.items():
                user_unique_dict[str(user_id)] = {
                    date_str: list(urls) for date_str, urls in date_urls.items()
                }
            url_last_report_dict = {}
            for user_id, url_times in self.url_last_report.items():
                url_last_report_dict[str(user_id)] = {
                    url: dt.isoformat() for url, dt in url_times.items()
                }
            url_first_report_dict = {}
            for user_id, url_dates in self.url_first_report.items():
                url_first_report_dict[str(user_id)] = url_dates
            data = {
                "user_unique_urls": user_unique_dict,
                "url_last_report": url_last_report_dict,
                "url_first_report": url_first_report_dict,
                "all_reported_urls": list(self.all_reported_urls),
                "last_updated": datetime.now().isoformat()
            }
            with open(self.data_file, "w") as f:
                json.dump(data, f, indent=2)
        except Exception as e:
            print(f"Failed to save report tracking: {e}")

    def load_data(self):
        try:
            if os.path.exists(self.data_file):
                with open(self.data_file, "r") as f:
                    data = json.load(f)
                self.user_unique_urls = defaultdict(lambda: defaultdict(set))
                raw_user_unique = data.get("user_unique_urls", {})
                for user_id_str, date_urls in raw_user_unique.items():
                    user_id = int(user_id_str)
                    for date_str, urls_list in date_urls.items():
                        self.user_unique_urls[user_id][date_str] = set(urls_list)
                self.url_last_report = defaultdict(dict)
                raw_url_last = data.get("url_last_report", {})
                for user_id_str, url_times in raw_url_last.items():
                    user_id = int(user_id_str)
                    for url, dt_str in url_times.items():
                        self.url_last_report[user_id][url] = datetime.fromisoformat(dt_str)
                self.url_first_report = defaultdict(dict)
                raw_url_first = data.get("url_first_report", {})
                for user_id_str, url_dates in raw_url_first.items():
                    user_id = int(user_id_str)
                    self.url_first_report[user_id] = url_dates
                self.all_reported_urls = set(data.get("all_reported_urls", []))
                self.clean_old_data()
        except Exception as e:
            print(f"Failed to load report tracking: {e}")
            self.user_unique_urls = defaultdict(lambda: defaultdict(set))
            self.url_last_report = defaultdict(dict)
            self.url_first_report = defaultdict(dict)
            self.all_reported_urls = set()

    def get_global_stats(self) -> Dict:
        with self.lock:
            today = date.today().isoformat()
            unique_urls_today = set()
            for user_urls in self.user_unique_urls.values():
                if today in user_urls:
                    unique_urls_today.update(user_urls[today])
            return {
                "total_unique_urls_ever": len(self.all_reported_urls),
                "unique_urls_today": len(unique_urls_today),
                "active_users": len(self.user_unique_urls)
            }

    def reset_user_reports(self, user_id: int) -> bool:
        with self.lock:
            found = False
            if user_id in self.user_unique_urls:
                del self.user_unique_urls[user_id]
                found = True
            if user_id in self.url_last_report:
                del self.url_last_report[user_id]
                found = True
            if user_id in self.url_first_report:
                del self.url_first_report[user_id]
                found = True
            if found:
                self.save_data()
            return found

class UserAgentManager:
    def __init__(self):
        self.user_agents = [
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
            "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 13.6; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.1; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
            "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0",
            "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
        ]
        self.used_agents = {}
        self.last_used_index = -1
        self.lock = threading.Lock()

    def get_random_user_agent(self, user_id: int = None) -> str:
        with self.lock:
            available_agents = [
                ua for i, ua in enumerate(self.user_agents)
                if i != self.last_used_index
            ]
            if not available_agents:
                available_agents = self.user_agents
            selected_agent = random.choice(available_agents)
            self.last_used_index = self.user_agents.index(selected_agent)
            if user_id:
                if user_id not in self.used_agents:
                    self.used_agents[user_id] = []
                self.used_agents[user_id].append({
                    "agent": selected_agent,
                    "time": datetime.now()
                })
            print(f"🦊 Selected Firefox User-Agent: {selected_agent[:60]}...")
            return selected_agent

@dataclass
class ScheduledReport:
    url: str
    user_id: int
    interval_hours: int
    created_at: datetime
    last_run: datetime
    next_run: datetime
    is_active: bool
    total_runs: int
    successful_runs: int
    failed_runs: int
    content_type: str = None  # TAMBAH
    keyword: str = None       # TAMBAH

    def to_dict(self):
        return {
            "url": self.url,
            "user_id": self.user_id,
            "interval_hours": self.interval_hours,
            "created_at": self.created_at.isoformat(),
            "last_run": self.last_run.isoformat() if self.last_run else None,
            "next_run": self.next_run.isoformat(),
            "is_active": self.is_active,
            "total_runs": self.total_runs,
            "successful_runs": self.successful_runs,
            "failed_runs": self.failed_runs,
            "content_type": self.content_type,  # TAMBAH
            "keyword": self.keyword              # TAMBAH
        }

    @classmethod
    def from_dict(cls, data):
        return cls(
            url=data["url"],
            user_id=data["user_id"],
            interval_hours=data["interval_hours"],
            created_at=datetime.fromisoformat(data["created_at"]),
            last_run=datetime.fromisoformat(data["last_run"]) if data["last_run"] else None,
            next_run=datetime.fromisoformat(data["next_run"]),
            is_active=data["is_active"],
            total_runs=data.get("total_runs", 0),
            successful_runs=data.get("successful_runs", 0),
            failed_runs=data.get("failed_runs", 0),
            content_type=data.get("content_type"),  # TAMBAH
            keyword=data.get("keyword")              # TAMBAH
        )

    def update_next_run(self):
        self.next_run = datetime.now() + timedelta(hours=self.interval_hours)

    def should_run(self) -> bool:
        if not self.is_active:
            return False
        now = datetime.now()
        should = now >= self.next_run
        if should:
            print(f"✅ Schedule ready: {self.url} (scheduled for {self.next_run.strftime('%H:%M:%S')}, now is {now.strftime('%H:%M:%S')})")
        return should

    def get_time_until_next_run(self) -> str:
        if not self.is_active:
            return "⏸️ PAUSED"
        remaining = self.next_run - datetime.now()
        if remaining.total_seconds() <= 0:
            return "▶️ RUNNING NOW"
        hours = int(remaining.total_seconds() // 3600)
        minutes = int((remaining.total_seconds() % 3600) // 60)
        if hours > 24:
            days = hours // 24
            return f"📅 {days}d {hours%24}h"
        elif hours > 0:
            return f"⏰ {hours}h {minutes}m"
        else:
            return f"⏱️ {minutes}m"

class ScheduleManager:
    def __init__(self, bot):
        self.bot = bot
        self.schedules: Dict[Tuple[int, str], ScheduledReport] = {}
        self.running = False
        self.task = None
        self.data_file = "schedules.json"
        self.lock = asyncio.Lock()
        self.load_schedules()

    def load_schedules(self):
        try:
            if os.path.exists(self.data_file):
                with open(self.data_file, "r") as f:
                    data = json.load(f)
                    for schedule_data in data.get("schedules", []):
                        schedule = ScheduledReport.from_dict(schedule_data)
                        key = (schedule.user_id, schedule.url)
                        self.schedules[key] = schedule
                    print(f"✅ Loaded {len(self.schedules)} schedules from disk")
        except Exception as e:
            print(f"Failed to load schedules: {e}")

    async def save_schedules(self):
        try:
            data = {
                "schedules": [schedule.to_dict() for schedule in self.schedules.values()],
                "last_saved": datetime.now().isoformat()
            }
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                lambda: json.dump(data, open(self.data_file, "w"), indent=2)
            )
        except Exception as e:
            print(f"Failed to save schedules: {e}")

    async def cleanup_expired_user_schedules(self) -> int:
        async with self.lock:
            removed_count = 0
            for key, schedule in list(self.schedules.items()):
                if schedule.is_active:
                    if not self.bot.is_authorized(schedule.user_id):
                        schedule.is_active = False
                        removed_count += 1
                        print(f"🛑 Auto-stopped schedule for expired user {schedule.user_id}: {schedule.url}")
            if removed_count > 0:
                await self.save_schedules()
                print(f"✅ Cleaned up {removed_count} schedule(s) from expired users")
            return removed_count

    async def add_schedule(self, user_id: int, url: str, interval_minutes: int, content_type: str = None, keyword: str = None) -> Tuple[bool, str]:
        if not self.bot.is_authorized(user_id):
            return (
                False,
                "⛔ Your access has expired. Contact admin for renewal."
            )
        min_interval_minutes = self.bot.config.get_min_schedule_interval()
        max_schedules = self.bot.config.get_max_schedules_per_user()
        if interval_minutes < min_interval_minutes:
            return (
                False,
                f"⚠️ Minimum interval is {min_interval_minutes} minutes"
            )
        if interval_minutes > 10080:
            return (
                False,
                f"⚠️ Maximum interval is 10080 minutes (7 days)"
            )
        interval_hours = interval_minutes / 60.0
        async with self.lock:
            user_active_schedules = len([
                s for s in self.schedules.values()
                if s.user_id == user_id and s.is_active
            ])
            key = (user_id, url)
            if key not in self.schedules or not self.schedules[key].is_active:
                if user_active_schedules >= max_schedules:
                    return (
                        False,
                        f"⚠️ Maximum {max_schedules} schedules per user. Remove a schedule first with /stop"
                    )
            if key in self.schedules:
                existing = self.schedules[key]
                if existing.is_active:
                    existing_minutes = int(existing.interval_hours * 60)
                    return (
                        False,
                        f"Already scheduled every {existing_minutes} minutes"
                    )
                else:
                    existing.is_active = True
                    existing.interval_hours = interval_hours
                    existing.content_type = content_type
                    existing.keyword = keyword
                    existing.total_runs = 0
                    existing.successful_runs = 0
                    existing.failed_runs = 0
                    existing.last_run = None
                    existing.created_at = datetime.now()
                    existing.update_next_run()
                    await self.save_schedules()
                    return (
                        True,
                        f"Schedule reactivated: Every {interval_minutes} minutes"
                    )
            next_run_time = datetime.now() + timedelta(hours=interval_hours)
            schedule = ScheduledReport(
                url=url,
                user_id=user_id,
                interval_hours=interval_hours,
                created_at=datetime.now(),
                last_run=None,
                next_run=next_run_time,
                is_active=True,
                total_runs=0,
                successful_runs=0,
                failed_runs=0,
                content_type=content_type,
                keyword=keyword
            )
            self.schedules[key] = schedule
            await self.save_schedules()
            print(f"📅 Schedule created: {url} will run at {next_run_time.strftime('%Y-%m-%d %H:%M:%S')}")
            return True, f"Scheduled: Every {interval_minutes} minutes"

    async def remove_schedule(self, user_id: int, url: str) -> bool:
        async with self.lock:
            key = (user_id, url)
            if key in self.schedules:
                self.schedules[key].is_active = False
                await self.save_schedules()
                return True
            return False

    async def get_user_schedules(self, user_id: int) -> List[ScheduledReport]:
        async with self.lock:
            return [
                s for s in self.schedules.values()
                if s.user_id == user_id and s.is_active
            ]

    async def clear_user_schedules(self, user_id: int) -> int:
        async with self.lock:
            count = 0
            for key, schedule in list(self.schedules.items()):
                if schedule.user_id == user_id:
                    schedule.is_active = False
                    count += 1
            if count > 0:
                await self.save_schedules()
            return count

    async def update_schedule_after_manual_report(self, user_id: int, url: str, success: bool = True):
        async with self.lock:
            key = (user_id, url)
            if key in self.schedules and self.schedules[key].is_active:
                schedule = self.schedules[key]
                schedule.last_run = datetime.now()
                schedule.total_runs += 1
                if success:
                    schedule.successful_runs += 1
                else:
                    schedule.failed_runs += 1
                schedule.update_next_run()
                await self.save_schedules()
                print(f"✅ Schedule updated for {url}: next run at {schedule.next_run.strftime('%Y-%m-%d %H:%M:%S')} (Success: {schedule.successful_runs}/{schedule.total_runs})")

    async def process_scheduled_report_async(self, schedule: ScheduledReport) -> bool:
        try:
            print(f"🔄 Processing scheduled report (ASYNC): {schedule.url} (User: {schedule.user_id})")
            if not self.bot.is_authorized(schedule.user_id):
                print(f"❌ User {schedule.user_id} access expired - stopping schedule for {schedule.url}")
                schedule.is_active = False
                await self.save_schedules()
                try:
                    await self.bot.application.bot.send_message(
                        chat_id=schedule.user_id,
                        text=f"⛔ *ACCESS EXPIRED*\n\n"
                            f"Your schedule for:\n`{schedule.url}`\n\n"
                            f"has been automatically stopped because your access has expired.\n\n"
                            f"Contact admin for access renewal.",
                        parse_mode=ParseMode.MARKDOWN
                    )
                except Exception as e:
                    print(f"⚠️ Could not send expiry notification to user {schedule.user_id}: {e}")
                return False
            can_report, reason = self.bot.report_tracker.can_report(schedule.user_id, schedule.url)
            if not can_report:
                if "cooldown" in reason.lower():
                    print(f"⏭️ Skipping scheduled report (cooldown): {schedule.url}")
                    schedule.last_run = datetime.now()
                    schedule.update_next_run()
                    return True
                else:
                    print(f"❌ Cannot process schedule: {reason}")
                    return False
            user_agent = self.bot.ua_manager.get_random_user_agent(schedule.user_id)
            print(f"⚡ Submitting to thread pool (non-blocking)...")
            success, _ = await asyncio.to_thread(
                self._execute_scheduled_report_in_thread,
                schedule,
                user_agent
            )
            schedule.total_runs += 1
            if success:
                self.bot.report_tracker.add_report(schedule.user_id, schedule.url)
                schedule.successful_runs += 1
                print(f"✅ Scheduled report successful: {schedule.url}")
            else:
                schedule.failed_runs += 1
                print(f"❌ Scheduled report failed: {schedule.url}")
            schedule.last_run = datetime.now()
            schedule.update_next_run()
            return True
        except Exception as e:
            print(f"❌ Error processing scheduled report: {e}")

            import traceback

            traceback.print_exc()
            schedule.failed_runs += 1
            schedule.last_run = datetime.now()
            schedule.update_next_run()
            return True

    def _execute_scheduled_report_in_thread(self, schedule: ScheduledReport, user_agent: str):
        try:
            print(f"🔧 Thread executing scheduled report: {schedule.url}")
            if schedule.content_type and schedule.keyword:
                print(f"   Content: {schedule.content_type}")
                print(f"   Keyword: {schedule.keyword}")
            reporter = EnhancedUltimateStealth(
                self.bot.ai_generator,
                user_agent,
                self.bot.config.get_proxy_mode(),
                self.bot.config.get_max_proxies_to_try(),
                self.bot.config.get_max_submit_retries_per_proxy()
            )
            result = reporter.execute_ai_powered_report(
                schedule.url,
                schedule.content_type,
                schedule.keyword
            )
            print(f"✅ Thread completed scheduled report: {schedule.url}")
            return result
        except Exception as e:
            print(f"❌ Thread execution error for {schedule.url}: {e}")

            import traceback

            traceback.print_exc()
            return False, None

    async def run_scheduler(self):
        print("🔄 Schedule manager started (ASYNC mode)")
        print("⏰ Checking schedules every 30 seconds")
        self.running = True
        cleanup_counter = 0
        cleanup_interval = 20
        while self.running:
            try:
                current_time = datetime.now()
                cleanup_counter += 1
                if cleanup_counter >= cleanup_interval:
                    print("\n🧹 Running periodic cleanup of expired user schedules...")
                    removed = await self.cleanup_expired_user_schedules()
                    if removed > 0:
                        print(f"✅ Removed {removed} schedule(s) from expired users")
                    cleanup_counter = 0
                schedules_to_run = []
                async with self.lock:
                    for schedule in list(self.schedules.values()):
                        if not self.running:
                            break
                        if schedule.should_run():
                            schedules_to_run.append(schedule)
                if schedules_to_run:
                    print(f"\n⏰ {current_time.strftime('%H:%M:%S')} - Found {len(schedules_to_run)} schedule(s) to process")
                    tasks = []
                    for schedule in schedules_to_run:
                        if not self.running:
                            break
                        print(f"🔄 Queuing: {schedule.url} (User: {schedule.user_id})")
                        task = asyncio.create_task(
                            self.process_scheduled_report_async(schedule)
                        )
                        tasks.append(task)
                    if tasks:
                        await asyncio.gather(*tasks, return_exceptions=True)
                        await self.save_schedules()
                else:
                    async with self.lock:
                        active_schedules = [s for s in self.schedules.values() if s.is_active]
                    if active_schedules:
                        next_schedule = min(
                            active_schedules,
                            key=lambda s: s.next_run,
                            default=None
                        )
                        if next_schedule:
                            time_until = (next_schedule.next_run - current_time).total_seconds()
                            if time_until > 0:
                                minutes = int(time_until / 60)
                                print(f"💤 Next schedule in {minutes} minutes ({next_schedule.url})")
                await asyncio.sleep(30)
            except Exception as e:
                print(f"❌ Scheduler error: {e}")

                import traceback

                traceback.print_exc()
                await asyncio.sleep(30)

    def start(self):
        if not self.running and not self.task:
            loop = asyncio.get_event_loop()
            self.task = loop.create_task(self.run_scheduler())
            print("✅ Scheduler started as async task")
            return True
        return False

    def stop(self):
        self.running = False
        if self.task:
            self.task.cancel()
            self.task = None
            print("🛑 Scheduler stopped")

class AIReportGenerator:
    def __init__(self, ai_api_url: str = "http://157.20.32.130:7006"):
        self.ai_api_url = ai_api_url.rstrip("/")
        self.session = requests.Session()
        self.session.headers.update({
            "Content-Type": "application/json",
            "User-Agent": "Report Bot AI Client"
        })

    def generate_custom_report(self, target_url: str, content_type: str, keyword: str) -> str:
        print("\n" + "=" * 80)
        print("AI CUSTOM REPORT GENERATION PROCESS")
        print("=" * 80)
        print(f"Target URL: {target_url}")
        print(f"Content Type: {content_type}")
        print(f"Keyword: {keyword}")
        try:
            prompt = f"""Anda adalah warga negara Indonesia yang mengajukan laporan legal removal kepada Google untuk penghapusan URL dari hasil pencarian Google Search.

TUGAS:
Hasilkan teks laporan hukum yang singkat, tegas, profesional, dan kredibel untuk meminta deindex URL yang terlibat perjudian online ilegal, baik konten masih aktif, sudah dihapus, dialihkan, maupun masih muncul sebagai cache atau hasil pencarian residual.

DATA LAPORAN:
- URL: {target_url}
- Kata kunci pencarian: "{keyword}"
- Kategori pelanggaran: Perjudian online ilegal

KETENTUAN ISI WAJIB:
- Nyatakan bahwa URL tersebut memuat, memfasilitasi, mengarahkan, atau sebelumnya menampilkan konten perjudian online ilegal
- Tegaskan bahwa URL masih muncul di hasil Google Search dan dapat diakses publik melalui kata kunci terkait
- Cantumkan dasar hukum Indonesia secara eksplisit dan akurat (UU ITE Pasal 27 ayat (2) jo. UU No.19 Tahun 2016 dan/atau KUHP Pasal 303) atau pasal yang masih berhubungan dengan tindakan ilegal tersebut
- Jelaskan dampak umum terhadap masyarakat Indonesia secara proporsional (misalnya kerugian sosial atau penyesatan pengguna), tanpa klaim berlebihan
- Akhiri dengan permintaan tegas agar URL dideindex atau dihapus dari indeks Google Search di wilayah Indonesia demi kepatuhan hukum lokal

ATURAN OUTPUT:
- Bahasa Indonesia formal, lugas, dan mendesak
- Panjang 250-350 karakter
- Maksimal 2-4 kalimat, tanpa penomoran, tanpa baris baru
- Teks polos tanpa simbol atau formatting khusus
- Struktur kalimat dan diksi HARUS bervariasi setiap generasi untuk menghindari pola berulang
- Tidak mengklaim sebagai aparat, lembaga resmi, atau perwakilan pemerintah
- Langsung ke inti substansi, tanpa pembuka atau penutup basa-basi
"""
            messages = [{"role": "user", "content": prompt}]
            print("\nSending AI generation request...")
            response = self.session.post(
                f"{self.ai_api_url}/api/simple-chat",
                json={"messages": messages},
                timeout=80, headers=ua
            )
            print(f"AI API Response Status: {response.status_code}")
            if response.status_code == 200:
                data = response.json()
                if data.get("success") and data.get("response"):
                    ai_text = data["response"].strip()
                    ai_text = ai_text.replace('"', "").replace("\n\n", " ").replace("\n", " ")
                    ai_text = " ".join(ai_text.split())
                    if len(ai_text) > 50:
                        self._display_ai_results(ai_text, target_url, is_fallback=False)
                        return ai_text
            print("AI generation failed, using custom template...")
            fallback_text = self._generate_custom_template(target_url, content_type, keyword)
            self._display_ai_results(fallback_text, target_url, is_fallback=True)
            return fallback_text
        except Exception as e:
            print(f"AI generation error: {e}")
            fallback_text = self._generate_custom_template(target_url, content_type, keyword)
            return fallback_text

    def _generate_custom_template(self, target_url: str, content_type: str, keyword: str) -> str:
        templates = [
            f"Situs {target_url} muncul di pencarian Google dengan Keyword '{keyword}' dan berisi konten {content_type} ilegal yang melanggar hukum Indonesia. Mohon segera blokir dan hapus dari hasil pencarian Google.",
            f"Laporan: Website {target_url} ditemukan melalui pencarian '{keyword}' di Google. Situs ini menampilkan {content_type} ilegal yang meresahkan masyarakat. Harap segera ditindak dan dihapus dari internet.",
            f"Website ilegal {target_url} terindeks di Google dengan keyword '{keyword}'. Konten {content_type} di situs ini melanggar hukum dan merugikan pengguna internet. Mohon segera diblokir.",
            f"Situs {target_url} yang berisi {content_type} ilegal mudah ditemukan di Google dengan Keyword '{keyword}'. Ini sangat berbahaya bagi masyarakat. Mohon segera hapus dari hasil pencarian."
        ]
        return random.choice(templates)

    def generate_varied_report(self, target_url: str, content_type: str = None, keyword: str = None) -> str:
        if content_type and keyword:
            return self.generate_custom_report(target_url, content_type, keyword)
        print("\n" + "=" * 80)
        print("AI REPORT GENERATION PROCESS")
        print("=" * 80)
        print(f"Target URL: {target_url}")
        try:
            domain_context = self._analyze_domain_type(target_url)
            print(f"Domain Analysis: {domain_context}")
            prompt = f"""Anda adalah petugas kepatuhan hukum profesional yang melaporkan situs web ilegal ke Google melalui layanan Google SafeBrowsing. Tulislah laporan singkat, faktual, efektif dan tajam yang akan meyakinkan Google untuk segera memblokir dan menghapusnya dari internet. Laporan tersebut harus berfokus pada pelanggaran hukum dan content ilegal, tulislah laporan kurang dari 350 karakter huruf dan angka, hanya teks, tanpa simbol apapun.
TARGETED WEBSITE: {target_url}
SITE TYPE: {domain_context}
"""
            messages = [{"role": "user", "content": prompt}]
            print("\nSending AI generation request...")
            response = self.session.post(
                f"{self.ai_api_url}/api/simple-chat",
                json={"messages": messages},
                timeout=30
            )
            print(f"AI API Response Status: {response.status_code}")
            if response.status_code == 200:
                data = response.json()
                if data.get("success") and data.get("response"):
                    ai_text = data["response"].strip()
                    ai_text = ai_text.replace('"', "").replace("\n\n", " ").replace("\n", " ")
                    ai_text = " ".join(ai_text.split())
                    if len(ai_text) > 50:
                        self._display_ai_results(ai_text, target_url)
                        return ai_text
            print("AI generation failed, using template...")
            fallback_text = self._generate_template_variation(target_url)
            self._display_ai_results(fallback_text, target_url, is_fallback=True)
            return fallback_text
        except Exception as e:
            print(f"AI generation error: {e}")
            fallback_text = self._generate_template_variation(target_url)
            return fallback_text

    def _display_ai_results(self, generated_text: str, target_url: str, is_fallback: bool = False):
        method = "TEMPLATE VARIATION" if is_fallback else "AI-GENERATED"
        print("\n" + "=" * 80)
        print(f"REPORT GENERATION COMPLETE - {method}")
        print("=" * 80)
        print(f"\nTARGET: {target_url}")
        print(f"LENGTH: {len(generated_text)} characters")
        print("\n" + "-" * 80)
        print("GENERATED REPORT TEXT:")
        print("-" * 80)
        print(f"\n{generated_text}\n")
        print("-" * 80)
        print("\n")

    def _generate_template_variation(self, target_url: str) -> str:
        domain_type = self._analyze_domain_type(target_url)
        variations = [
            f"Situs ini {domain_type} merupakan web perjudian online ilegal yang melanggar hukum dan meresahkan masyarakat. Mohon agar segera diblokir dan dihapus dari peredaran internet."
        ]
        return random.choice(variations)

    def _analyze_domain_type(self, url: str) -> str:
        url_lower = url.lower()
        if any(term in url_lower for term in ["slot", "casino", "bet", "gambling", "poker", "jackpot"]):
            return "illegal online gambling platform slot"
        elif any(term in url_lower for term in ["phish", "fake", "scam", "fraud"]):
            return "phishing/fraud website"
        else:
            return "illegal website with harmful activities"

def create_proxy_auth_extension(proxy: Dict) -> str:
    protocol = proxy.get("protocol", "http").lower()
    scheme_mapping = {
        "http": "http",
        "https": "https",
        "socks4": "socks4",
        "socks5": "socks5",
        "socks": "socks5"
    }
    scheme = scheme_mapping.get(protocol, "http")
    manifest_json = """
{
    "version": "1.0.0",
    "manifest_version": 2,
    "name": "Chrome Proxy Auth",
    "permissions": [
        "proxy",
        "tabs",
        "unlimitedStorage",
        "storage",
        "<all_urls>",
        "webRequest",
        "webRequestBlocking"
    ],
    "background": {
        "scripts": ["background.js"]
    },
    "minimum_chrome_version":"22.0.0"
}
"""
    background_js = """
var config = {
    mode: "fixed_servers",
    rules: {
        singleProxy: {
            scheme: "%s",
            host: "%s",
            port: parseInt(%s)
        },
        bypassList: ["localhost", "127.0.0.1"]
    }
};

chrome.proxy.settings.set({value: config, scope: "regular"}, function() {
    console.log('Proxy configured: %s://%s:%s');
});

function callbackFn(details) {
    return {
        authCredentials: {
            username: "%s",
            password: "%s"
        }
    };
}

chrome.webRequest.onAuthRequired.addListener(
    callbackFn,
    {urls: ["<all_urls>"]},
    ['blocking']
);

console.log('Proxy authentication configured for %s proxy');
""" % (
        scheme, proxy["host"], proxy["port"],
        scheme, proxy["host"], proxy["port"],
        proxy["user"], proxy["password"],
        scheme.upper()
    )
    plugin_file = f'proxy_auth_plugin_{scheme}_{proxy["host"]}_{proxy["port"]}.zip'
    with zipfile.ZipFile(plugin_file, "w") as zp:
        zp.writestr("manifest.json", manifest_json)
        zp.writestr("background.js", background_js)
    return plugin_file

class EnhancedUltimateStealth:
    """
    Versi stable menggunakan Playwright Firefox + manual stealth.
    Lebih reliable daripada Camoufox yang sering konflik dependency.
    """
    
    _last_submission_time = None
    _submission_lock = threading.Lock()
    _min_submission_interval = 8  # <<<< UBAH dari 3 ke 8

    def __init__(
        self,
        ai_generator,
        user_agent: str = None,
        proxy_mode: str = "random",
        max_proxies_to_try: int = 6,  # <<<< UBAH dari 4 ke 6
        max_submit_retries_per_proxy: int = 2  # <<<< UBAH dari 3 ke 2
    ):
        self.playwright_instance = None
        self.browser = None
        self.page = None
        self.context = None
        self.ai_generator = ai_generator
        self.user_agent = user_agent or "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"
        self.proxy_mode = proxy_mode
        self.current_proxy = None
        self.use_proxy = True
        self.max_proxies_to_try = max_proxies_to_try
        self.max_submit_retries_per_proxy = max_submit_retries_per_proxy
        self._current_url_target = None
        self._current_report_text = None
        self._cached_ai_text = None
        self._cached_url = None
        self._cached_content_type = None
        self._cached_keyword = None
        self._submit_api_status = None
        self._submit_api_response = None
        self._recaptcha_token_seen = False
        self._submit_clicked = False
        self._outgoing_post_seen = False

    # =========================================================================
    # RATE LIMIT
    # =========================================================================

    @classmethod
    def _wait_for_rate_limit(cls):
        with cls._submission_lock:
            if cls._last_submission_time:
                elapsed = (datetime.now() - cls._last_submission_time).total_seconds()
                if elapsed < cls._min_submission_interval:
                    wait_time = cls._min_submission_interval - elapsed
                    print("⏳ Rate limit: waiting %.1fs..." % wait_time)
                    time.sleep(wait_time)
            cls._last_submission_time = datetime.now()

    def __del__(self):
        try:
            self.close_browser()
        except Exception:
            pass

    # =========================================================================
    # HUMAN BEHAVIOR SIMULATION
    # =========================================================================

    def _human_delay(self, min_sec=0.3, max_sec=0.8):
        mean = (min_sec + max_sec) / 2
        std_dev = (max_sec - min_sec) / 4
        delay = max(min_sec, min(max_sec, random.gauss(mean, std_dev)))
        delay += random.uniform(0.02, 0.08)
        if random.random() < 0.08:
            delay += random.uniform(0.3, 0.6)
        time.sleep(delay)

    def _bezier_point(self, t, p0, p1, p2, p3):
        u = 1 - t
        return u**3 * p0 + 3 * u**2 * t * p1 + 3 * u * t**2 * p2 + t**3 * p3

    def _human_mouse_move(self, target_x, target_y, steps=None):
        try:
            if not self.page:
                return
            try:
                vp = self.page.viewport_size
                if not vp:
                    vp = {"width": 1280, "height": 720}
                start_x = random.randint(50, vp["width"] // 2)
                start_y = random.randint(50, vp["height"] // 2)
            except Exception:
                start_x = random.randint(100, 400)
                start_y = random.randint(100, 400)

            distance = math.sqrt((target_x - start_x)**2 + (target_y - start_y)**2)
            if steps is None:
                steps = max(8, min(25, int(distance / 15)))

            cp1_x = (start_x + target_x) / 2 + random.uniform(-80, 80)
            cp1_y = start_y + random.uniform(-40, 40)
            cp2_x = (start_x + target_x) / 2 + random.uniform(-80, 80)
            cp2_y = target_y + random.uniform(-40, 40)

            for i in range(steps + 1):
                t = i / steps
                t = t * t * (3 - 2 * t)
                x = self._bezier_point(t, start_x, cp1_x, cp2_x, target_x)
                y = self._bezier_point(t, start_y, cp1_y, cp2_y, target_y)
                x += random.uniform(-1.5, 1.5)
                y += random.uniform(-1.5, 1.5)
                self.page.mouse.move(x, y)
                time.sleep(random.uniform(0.003, 0.015))
        except Exception:
            try:
                self.page.mouse.move(target_x, target_y)
            except Exception:
                pass

    def _human_scroll(self, direction="down", amount=None):
        try:
            if amount is None:
                amount = random.randint(80, 250)
            delta = amount if direction == "down" else -amount
            remaining = abs(delta)
            sign = 1 if delta > 0 else -1
            while remaining > 0:
                chunk = min(remaining, random.randint(15, 45))
                self.page.mouse.wheel(0, chunk * sign)
                remaining -= chunk
                time.sleep(random.uniform(0.01, 0.03))
        except Exception:
            pass

    def _human_type(self, element, text, speed="normal"):
        """Type dengan keystroke dynamics seperti manusia"""
        try:
            element.click()
            time.sleep(random.uniform(0.15, 0.35))
            element.fill("")
            time.sleep(random.uniform(0.08, 0.18))
            element.click()
            time.sleep(random.uniform(0.1, 0.2))

            speed_map = {
                "slow": (80, 180),
                "normal": (35, 90),
                "fast": (20, 50)
            }
            min_d, max_d = speed_map.get(speed, (35, 90))

            # Typing pattern: cepat di awal, lambat di akhir (natural)
            words = text.split(' ')
            for word_idx, word in enumerate(words):
                for char_idx, char in enumerate(word):
                    # Base delay
                    delay = random.uniform(min_d, max_d) / 1000
                    
                    # Slower at start of word
                    if char_idx == 0:
                        delay += random.uniform(0.05, 0.15)
                    
                    # Slower for special chars
                    if char in '.,;:!?/@#$%^&*()_-+=[]{}|\\':
                        delay += random.uniform(0.03, 0.08)
                    
                    # Random micro-pauses (thinking)
                    if random.random() < 0.08:
                        delay += random.uniform(0.3, 0.8)
                    
                    # Typo simulation (backspace + retype)
                    if random.random() < 0.03:  # 3% chance typo
                        wrong_char = random.choice('asdfghjkl')
                        self.page.keyboard.type(wrong_char)
                        time.sleep(random.uniform(0.1, 0.2))
                        self.page.keyboard.press("Backspace")
                        time.sleep(random.uniform(0.15, 0.3))
                    
                    self.page.keyboard.type(char)
                    time.sleep(delay)
                
                # Space setelah word (kecuali word terakhir)
                if word_idx < len(words) - 1:
                    self.page.keyboard.press("Space")
                    time.sleep(random.uniform(0.05, 0.15))
                    
        except Exception as e:
            print(f"   ⚠️ Type error: {e}")
            try:
                element.fill(text)
            except:
                pass

    def _random_mouse_wander(self, duration=2.0):
        """Extended mouse wandering untuk build reCAPTCHA score."""
        try:
            vp = self.page.viewport_size
            if not vp:
                vp = {"width": 1280, "height": 720}
            start = time.time()
            while time.time() - start < duration:
                x = random.randint(30, vp["width"] - 30)
                y = random.randint(30, vp["height"] - 30)
                self._human_mouse_move(x, y, steps=random.randint(5, 12))
                if random.random() < 0.3:
                    time.sleep(random.uniform(0.3, 0.8))
                else:
                    time.sleep(random.uniform(0.05, 0.2))
        except Exception:
            time.sleep(duration)

    def _extended_warmup(self):
        """
        Ultra-realistic warmup - minimal 25-35 detik.
        Fokus: natural mouse movement + reading behavior + form interaction
        """
        try:
            print("   🔥 Ultra-realistic warmup (25-35s)...")
            
            # Phase 1: Initial page exploration (6-10s)
            print("      Phase 1: Page exploration...")
            try:
                # Scroll ke berbagai bagian halaman seperti orang baca
                viewport = self.page.viewport_size or {"width": 1280, "height": 720}
                
                # Scroll down perlahan seperti membaca
                for _ in range(3):
                    self._human_scroll("down", random.randint(150, 300))
                    time.sleep(random.uniform(1.5, 3.0))  # Pause baca content
                    
                    # Random mouse movement di area yang di-scroll
                    for _ in range(2):
                        x = random.randint(100, viewport["width"] - 100)
                        y = random.randint(200, min(500, viewport["height"] - 100))
                        self._human_mouse_move(x, y, steps=random.randint(10, 20))
                        time.sleep(random.uniform(0.3, 0.8))
                
                # Scroll back up seperti review
                self._human_scroll("up", random.randint(200, 400))
                time.sleep(random.uniform(2.0, 3.5))
                
            except Exception as e:
                print(f"      Phase 1 warning: {e}")
                time.sleep(8)
            
            # Phase 2: Form field interaction (8-12s)
            print("      Phase 2: Form field interaction...")
            try:
                # Hover ke setiap form field seperti orang mau isi form
                fields = [
                    "input[formcontrolname='url']",
                    "textarea[formcontrolname='details']",
                    "mat-select[formcontrolname='l1Taxonomy']",
                    "mat-select[formcontrolname='l3Taxonomy']"
                ]
                
                for field_sel in fields:
                    try:
                        field = self.page.locator(field_sel).first
                        if field.is_visible(timeout=2000):
                            # Scroll ke field
                            field.scroll_into_view_if_needed()
                            time.sleep(random.uniform(0.5, 1.0))
                            
                            # Move mouse ke field
                            box = field.bounding_box()
                            if box:
                                # Move ke tengah field
                                self._human_mouse_move(
                                    box['x'] + box['width'] / 2 + random.uniform(-20, 20),
                                    box['y'] + box['height'] / 2 + random.uniform(-10, 10),
                                    steps=random.randint(12, 25)
                                )
                                time.sleep(random.uniform(0.8, 1.5))
                                
                                # Kadang click field (tapi jangan isi)
                                if random.random() < 0.6:
                                    field.click(timeout=2000)
                                    time.sleep(random.uniform(0.3, 0.7))
                                    # Click di luar untuk blur
                                    self.page.mouse.click(
                                        random.randint(50, 200),
                                        random.randint(50, 200)
                                    )
                                    time.sleep(random.uniform(0.5, 1.0))
                            
                    except Exception:
                        continue
                        
            except Exception as e:
                print(f"      Phase 2 warning: {e}")
                time.sleep(10)
            
            # Phase 3: Text/label reading behavior (6-8s)
            print("      Phase 3: Reading labels...")
            try:
                # Hover ke berbagai text elements seperti orang baca
                selectors = ["h1", "h2", "label", "p", "mat-label", "span.mdc-button__label"]
                
                for sel in selectors:
                    try:
                        elems = self.page.locator(sel).all()
                        if len(elems) > 0:
                            # Pilih 2-3 elemen random
                            sample_count = min(len(elems), random.randint(2, 3))
                            for elem in random.sample(elems, sample_count):
                                if elem.is_visible(timeout=500):
                                    box = elem.bounding_box()
                                    if box:
                                        # Move ke text seperti orang baca
                                        self._human_mouse_move(
                                            box['x'] + random.uniform(10, box['width'] - 10),
                                            box['y'] + random.uniform(5, box['height'] - 5),
                                            steps=random.randint(8, 15)
                                        )
                                        # Pause seperti membaca
                                        time.sleep(random.uniform(0.8, 2.0))
                    except Exception:
                        continue
                        
            except Exception as e:
                print(f"      Phase 3 warning: {e}")
                time.sleep(6)
            
            # Phase 4: Random thinking behavior (5-7s)
            print("      Phase 4: Thinking simulation...")
            try:
                # Gerakan mouse random seperti orang bingung/mikir
                for _ in range(random.randint(5, 8)):
                    x = random.randint(100, viewport["width"] - 100)
                    y = random.randint(100, viewport["height"] - 100)
                    self._human_mouse_move(x, y, steps=random.randint(10, 20))
                    # Pause lebih lama = thinking
                    time.sleep(random.uniform(0.8, 1.8))
                    
            except Exception as e:
                print(f"      Phase 4 warning: {e}")
                time.sleep(5)
            
            # Phase 5: Final preparation (3-5s)
            print("      Phase 5: Final prep...")
            self._human_scroll("down", random.randint(100, 200))
            time.sleep(random.uniform(1.2, 2.0))
            self._human_scroll("up", random.randint(50, 100))
            time.sleep(random.uniform(1.5, 2.5))
            
            print("   ✅ Ultra-realistic warmup complete (~30s)")
            
        except Exception as e:
            print(f"   ⚠️ Warmup error: {e}")
            time.sleep(15)  # Fallback minimal
    # =========================================================================
    # NETWORK INTERCEPT
    # =========================================================================

    def _setup_network_intercept(self):
        self._submit_api_status = None
        self._submit_api_response = None
        self._recaptcha_token_seen = False
        self._submit_clicked = False
        self._outgoing_post_seen = False

        def on_response(response):
            try:
                url = response.url.lower()
                status = response.status

                if 'recaptcha' in url and ('reload' in url or 'enterprise' in url or 'anchor' in url):
                    if status == 200:
                        self._recaptcha_token_seen = True
                        print("   📡 reCAPTCHA token captured (%d)" % status)
                    return

                if not self._submit_clicked:
                    return

                is_submit = False
                if 'batchexecute' in url and 'safebrowsing' in url:
                    is_submit = True
                if not is_submit:
                    for kw in ['safebrowsingsubmission', 'clientreport', 'phishreport',
                               'submitreport', 'report_phish/submit']:
                        if kw in url:
                            is_submit = True
                            break

                if is_submit:
                    for skip in ['.js', '.css', '.png', '.ico', '.woff', '.svg',
                                 '.gif', '.jpg', 'gstatic.com', 'fonts.']:
                        if skip in url:
                            is_submit = False
                            break

                if is_submit and self._submit_api_status is None:
                    try:
                        body = response.text()
                        self._submit_api_response = body
                    except Exception:
                        body = ""
                        self._submit_api_response = ""

                    body_lower = body.lower() if body else ""
                    error_keywords = [
                        'generic::internal', 'rpcerror', 'generic::unavailable',
                        'generic::permission', 'generic::invalid',
                        'error occurred', 'terjadi error',
                    ]
                    is_rpc_error = any(ek in body_lower for ek in error_keywords)

                    if status == 200 and not is_rpc_error:
                        self._submit_api_status = True
                        print("   📡 ✅ SUBMIT API → 200 OK (clean): %s" % url[:100])
                    elif is_rpc_error:
                        self._submit_api_status = False
                        print("   📡 ❌ SUBMIT API → RPC ERROR: %s" % url[:100])
                        print("   📡 Body: %s" % body[:300])
                    elif status >= 400:
                        self._submit_api_status = False
                        print("   📡 ❌ SUBMIT API → HTTP %d" % status)
            except Exception:
                pass

        def on_request(request):
            try:
                if request.method == "POST" and self._submit_clicked:
                    url = request.url.lower()
                    if 'batchexecute' in url or 'safebrowsingsubmission' in url:
                        self._outgoing_post_seen = True
                        print("   📤 POST sent → %s" % url[:100])
            except Exception:
                pass

        self.page.on("response", on_response)
        self.page.on("request", on_request)
        print("   📡 Network intercept active")

    # =========================================================================
    # PROXY
    # =========================================================================

    def _get_proxy_for_playwright(self, proxy):
        """Format proxy untuk Playwright."""
        if not proxy:
            return None
        host = proxy['host']
        port = proxy['port']
        user = proxy.get('user', '')
        password = proxy.get('password', '')
        protocol = proxy.get('protocol', 'http').lower()

        if protocol in ['socks5', 'socks']:
            server_url = "socks5://%s:%s" % (host, port)
        else:
            server_url = "http://%s:%s" % (host, port)

        config = {"server": server_url}
        if user and password:
            config["username"] = str(user)
            config["password"] = str(password)
        return config

    def _test_proxy_quality(self, proxy):
        import requests as req
        import urllib.parse
        session = None
        try:
            host = proxy['host']
            port = proxy['port']
            user = proxy.get('user', '')
            password = proxy.get('password', '')
            if user and password:
                eu = urllib.parse.quote(str(user), safe='')
                ep = urllib.parse.quote(str(password), safe='')
                purl = "http://%s:%s@%s:%s" % (eu, ep, host, port)
            else:
                purl = "http://%s:%s" % (host, port)
            session = req.Session()
            session.headers.update({
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0'
            })
            r = session.get('http://ip-api.com/json/',
                          proxies={'http': purl, 'https': purl},
                          timeout=8, verify=False)
            return r.status_code == 200
        except Exception:
            return False
        finally:
            if session:
                try:
                    session.close()
                except Exception:
                    pass

    def _find_working_proxy(self):
        global proxy_manager
        tested = []
        print("\n🔍 Finding working proxy...")
        for i in range(6):
            try:
                if i == 0:
                    c = proxy_manager.get_best_proxy()
                elif self.proxy_mode == "random":
                    c = proxy_manager.get_random_proxy()
                else:
                    c = proxy_manager.get_next_proxy()
            except Exception:
                c = proxy_manager.get_random_proxy()
            key = "%s:%s" % (c['host'], c['port'])
            if key in tested:
                continue
            tested.append(key)
            print("   Testing #%d: %s" % (i + 1, key))
            if self._test_proxy_quality(c):
                print("   ✅ OK")
                return c
            print("   ❌ Failed")
        return None

    # =========================================================================
    # BROWSER - PLAYWRIGHT FIREFOX + MANUAL STEALTH
    # =========================================================================

    def create_stealth_browser(self, retry_count=0, max_retries=2):
        """
        Playwright Firefox dengan manual stealth scripts.
        Lebih stable daripada Camoufox.
        """
        global proxy_manager
        try:
            if self.use_proxy and proxy_manager.get_total_proxies() > 0:
                self.current_proxy = self._find_working_proxy()
                if not self.current_proxy:
                    print("❌ No working proxy found")
                    return False
            else:
                print("❌ No proxies available")
                return False

            proxy_config = self._get_proxy_for_playwright(self.current_proxy)
            if not proxy_config:
                return False

            window_sizes = [
                (1920, 1080), (1366, 768), (1536, 864),
                (1440, 900), (1600, 900), (1280, 720),
            ]
            w, h = random.choice(window_sizes)

            print("🦊 Starting Playwright Firefox (%dx%d)..." % (w, h))
            print("   Proxy: %s:%s" % (self.current_proxy['host'], self.current_proxy['port']))

            # Start Playwright
            self.playwright_instance = sync_playwright().start()
            
            # Launch Firefox browser
            self.browser = self.playwright_instance.firefox.launch(
                headless=True,
                proxy=proxy_config,
                firefox_user_prefs={
                    "privacy.trackingprotection.enabled": True,
                    "privacy.resistFingerprinting": False,  # Jangan terlalu agresif
                    "webgl.disabled": False,
                    "media.peerconnection.enabled": False,
                    "geo.enabled": False,
                }
            )
            
            # Create context
            self.context = self.browser.new_context(
                viewport={"width": w, "height": h},
                user_agent=self.user_agent,
                locale="id-ID",
                timezone_id="Asia/Jakarta",
                geolocation={"latitude": -6.2088, "longitude": 106.8456},  # Jakarta
                permissions=["geolocation"],
                device_scale_factor=1,
                has_touch=False,
                is_mobile=False,
            )
            
            # Create page
            self.page = self.context.new_page()
            
            # Apply manual stealth scripts
            print("   🛡️ Applying anti-detection scripts...")
            _apply_stealth_scripts(self.page)
            
            self.page.set_default_timeout(35000)
            self.page.set_default_navigation_timeout(50000)

            print("✅ Playwright Firefox ready (manual stealth)")
            return True

        except Exception as e:
            print("❌ Browser error: %s" % str(e))
            import traceback
            traceback.print_exc()
            self.close_browser()
            if retry_count < max_retries:
                print("   Retrying... (%d/%d)" % (retry_count + 1, max_retries))
                time.sleep(3)
                return self.create_stealth_browser(retry_count + 1, max_retries)
            return False

    # =========================================================================
    # NAVIGATION
    # =========================================================================

    def natural_navigation(self, retry_count=0, max_retries=2):
        global proxy_manager
        try:
            print("🌐 Navigating to SafeBrowsing...")

            # Step 1: Google warmup
            print("   [1] Google warmup...")
            try:
                self.page.goto("https://www.google.com",
                            wait_until="domcontentloaded", timeout=20000)
                
                # LEBIH LAMA - biarkan settle
                self._human_delay(3.0, 5.0)

                # Accept cookies
                for sel in ["#L2AGLb",
                        "button:has-text('Accept all')",
                        "button:has-text('Terima semua')",
                        "button:has-text('I agree')"]:
                    try:
                        btn = self.page.locator(sel).first
                        if btn.is_visible(timeout=2000):
                            self._human_delay(0.5, 1.0)
                            btn.click()
                            self._human_delay(1.0, 1.5)
                            break
                    except Exception:
                        continue

                # Extended interaction
                self._random_mouse_wander(duration=random.uniform(3.0, 5.0))
                self._human_scroll("down", random.randint(100, 200))
                time.sleep(random.uniform(1.2, 2.0))
                self._human_scroll("up", random.randint(50, 100))
                time.sleep(random.uniform(0.8, 1.5))

            except Exception as e:
                print("   ⚠️ Google warmup: %s" % str(e)[:80])

            # CRITICAL: Longer delay sebelum SafeBrowsing
            print("   ⏳ Settling before SafeBrowsing...")
            self._human_delay(random.uniform(4.0, 6.0))

            # Step 2: Navigate to SafeBrowsing
            print("   [2] SafeBrowsing page...")
            try:
                self.page.goto(
                    "https://safebrowsing.google.com/safebrowsing/report_phish/?hl=id",
                    wait_until="domcontentloaded",
                    timeout=50000
                )
            except PlaywrightTimeoutError:
                print("   ⚠️ Navigation timeout, waiting for load...")
                self._human_delay(3, 5)

            # Wait for networkidle
            try:
                self.page.wait_for_load_state("networkidle", timeout=20000)
            except Exception:
                pass

            # LEBIH LAMA - biarkan halaman settle
            self._human_delay(4.0, 6.0)

            # Verify page
            if "safebrowsing" not in self.page.url.lower():
                print("   ❌ Wrong page: %s" % self.page.url)
                return False

            # Accept cookies if appears
            for sel in ["#L2AGLb",
                        "button:has-text('Accept all')",
                        "button:has-text('Terima semua')"]:
                try:
                    btn = self.page.locator(sel).first
                    if btn.is_visible(timeout=2000):
                        btn.click()
                        self._human_delay(1.0, 1.5)
                        break
                except Exception:
                    continue

            # Step 3: Wait for reCAPTCHA
            print("   [3] Waiting reCAPTCHA...")
            
            # CRITICAL: Biarkan halaman settle dulu
            print("   ⏳ Letting page settle completely...")
            time.sleep(random.uniform(3.0, 5.0))
            
            recaptcha_ready = False
            for _ in range(30):
                s = self.page.evaluate("""() => {
                    if (typeof grecaptcha !== 'undefined') {
                        if (typeof grecaptcha.execute === 'function') return 'standard';
                        if (grecaptcha.enterprise && typeof grecaptcha.enterprise.execute === 'function') return 'enterprise';
                        return 'partial';
                    }
                    if (window.___grecaptcha_cfg) return 'cfg';
                    if (document.querySelectorAll('iframe[src*="recaptcha"]').length > 0) return 'iframe';
                    if (document.querySelectorAll('script[src*="recaptcha"]').length > 0) return 'script';
                    return 'waiting';
                }""")
                if s in ('standard', 'enterprise', 'partial', 'cfg', 'iframe'):
                    print("   ✅ reCAPTCHA status: %s" % s)
                    recaptcha_ready = True
                    break
                time.sleep(0.5)

            if not recaptcha_ready:
                print("   ⚠️ reCAPTCHA slow to load, continuing anyway...")

            # LEBIH LAMA lagi
            self._human_delay(3.0, 5.0)

            # Step 4: Wait for form fields
            print("   [4] Waiting form...")
            try:
                self.page.wait_for_selector(
                    "input[formcontrolname='url']",
                    state="visible", timeout=25000
                )
                self.page.wait_for_selector(
                    "textarea[formcontrolname='details']",
                    state="visible", timeout=15000
                )
            except Exception:
                print("   ❌ Form not found!")
                return False

            # Step 5: Extended warmup + network intercept
            self._extended_warmup()
            self._setup_network_intercept()

            print("✅ Navigation complete!")
            if self.current_proxy:
                proxy_manager.mark_proxy_success(self.current_proxy)
            return True

        except Exception as e:
            print("❌ Navigation failed: %s" % str(e))
            import traceback
            traceback.print_exc()
            if self.current_proxy:
                proxy_manager.mark_proxy_failed(self.current_proxy)
            if retry_count < max_retries:
                self.close_browser()
                time.sleep(random.uniform(3, 5))
                if not self.create_stealth_browser(0, 1):
                    return False
                return self.natural_navigation(retry_count + 1, max_retries)
            return False

    # =========================================================================
    # RECAPTCHA TOKEN MANAGEMENT
    # =========================================================================

    def _force_fresh_recaptcha_token(self):
        """Force-execute reCAPTCHA untuk mendapatkan token FRESH."""
        try:
            print("   🔑 Requesting fresh reCAPTCHA token...")

            self._recaptcha_token_seen = False

            result = self.page.evaluate("""() => {
                return new Promise((resolve) => {
                    try {
                        let siteKey = null;
                        
                        const el = document.querySelector('[data-sitekey]');
                        if (el) siteKey = el.getAttribute('data-sitekey');
                        
                        if (!siteKey) {
                            document.querySelectorAll('script[src*="recaptcha"]').forEach(s => {
                                const m = s.src.match(/render=([^&]+)/);
                                if (m && m[1] !== 'explicit') siteKey = m[1];
                            });
                        }
                        
                        if (!siteKey && window.___grecaptcha_cfg) {
                            const cfg = window.___grecaptcha_cfg;
                            if (cfg.clients) {
                                for (const key in cfg.clients) {
                                    try {
                                        const json = JSON.stringify(cfg.clients[key]);
                                        const m = json.match(/"sitekey"\\s*:\\s*"([^"]+)"/i);
                                        if (m) { siteKey = m[1]; break; }
                                    } catch(e) {}
                                }
                            }
                        }
                        
                        if (!siteKey) {
                            resolve('no_sitekey');
                            return;
                        }
                        
                        const doExecute = (executor) => {
                            executor.execute(siteKey, {action: 'submit'})
                                .then(token => {
                                    document.querySelectorAll(
                                        'textarea[name*="recaptcha"], input[name*="recaptcha"], ' +
                                        'input[name*="token"], textarea[name*="token"]'
                                    ).forEach(el => { el.value = token; });
                                    
                                    resolve('ok:' + token.substring(0, 30));
                                })
                                .catch(err => resolve('exec_error:' + err.message));
                        };
                        
                        if (typeof grecaptcha !== 'undefined') {
                            if (grecaptcha.enterprise && typeof grecaptcha.enterprise.execute === 'function') {
                                grecaptcha.enterprise.ready(() => doExecute(grecaptcha.enterprise));
                            } else if (typeof grecaptcha.execute === 'function') {
                                grecaptcha.ready(() => doExecute(grecaptcha));
                            } else {
                                resolve('no_execute_fn');
                            }
                        } else {
                            resolve('no_grecaptcha');
                        }
                        
                        setTimeout(() => resolve('timeout'), 12000);
                    } catch(e) {
                        resolve('error:' + e.message);
                    }
                });
            }""")

            print("   🔑 reCAPTCHA result: %s" % str(result)[:80])

            if result and str(result).startswith('ok:'):
                self._recaptcha_token_seen = True
                print("   ✅ Fresh token obtained!")
                time.sleep(0.5)
                return True
            else:
                print("   ⚠️ Token issue: %s (will try submit anyway)" % result)
                return True

        except Exception as e:
            print("   ⚠️ reCAPTCHA error: %s" % str(e)[:80])
            return True

    # =========================================================================
    # FORM FILLING
    # =========================================================================

    def ai_powered_paste_url(self, url):
        try:
            print("📝 URL: %s" % url)
            self._current_url_target = url

            field = self.page.locator("input[formcontrolname='url']")
            field.scroll_into_view_if_needed()
            self._human_delay(0.3, 0.6)

            box = field.bounding_box()
            if box:
                self._human_mouse_move(
                    box['x'] + random.uniform(10, box['width'] - 10),
                    box['y'] + random.uniform(3, box['height'] - 3)
                )
            self._human_delay(0.15, 0.3)

            self._human_type(field, url, speed="normal")
            self._human_delay(0.3, 0.5)

            # Trigger Angular change detection
            self.page.evaluate("""(url) => {
                const f = document.querySelector('input[formcontrolname="url"]');
                if (f) {
                    f.value = url;
                    f.dispatchEvent(new Event('input', {bubbles: true}));
                    f.dispatchEvent(new Event('change', {bubbles: true}));
                    f.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
                    f.classList.add('ng-touched', 'ng-dirty', 'ng-valid');
                    f.classList.remove('ng-untouched', 'ng-pristine', 'ng-invalid');
                }
            }""", url)

            self.page.keyboard.press("Tab")
            self._human_delay(0.3, 0.5)
            print("   ✅ URL filled")
            return True
        except Exception as e:
            print("   ❌ URL failed: %s" % str(e))
            try:
                self.page.locator("input[formcontrolname='url']").fill(url)
                return True
            except Exception:
                return False

    def ai_powered_paste_details(self, target_url, content_type=None, keyword=None):
        try:
            if (self._cached_ai_text and self._cached_url == target_url and
                    self._cached_content_type == content_type and
                    self._cached_keyword == keyword):
                text = self._cached_ai_text
            else:
                if content_type and keyword:
                    text = self.ai_generator.generate_custom_report(
                        target_url, content_type, keyword)
                else:
                    text = self.ai_generator.generate_varied_report(target_url)
                self._cached_ai_text = text
                self._cached_url = target_url
                self._cached_content_type = content_type
                self._cached_keyword = keyword

            self._current_report_text = text
            print("📝 Details (%d chars)" % len(text))

            field = self.page.locator("textarea[formcontrolname='details']")
            field.scroll_into_view_if_needed()
            self._human_delay(0.3, 0.6)

            box = field.bounding_box()
            if box:
                self._human_mouse_move(
                    box['x'] + random.uniform(10, box['width'] - 10),
                    box['y'] + random.uniform(5, box['height'] / 2)
                )
            self._human_delay(0.15, 0.3)

            # Ketik sebagian, paste sisanya
            if len(text) > 60:
                first_part = text[:45]
                rest_part = text[45:]
                field.click()
                time.sleep(0.1)
                field.fill("")
                time.sleep(0.05)
                field.click()
                for char in first_part:
                    self.page.keyboard.type(char)
                    time.sleep(random.uniform(0.02, 0.06))
                time.sleep(random.uniform(0.3, 0.5))
                self.page.evaluate("""(rest) => {
                    const f = document.querySelector('textarea[formcontrolname="details"]');
                    if (f) {
                        f.value = f.value + rest;
                        f.dispatchEvent(new Event('input', {bubbles: true}));
                    }
                }""", rest_part)
            else:
                self._human_type(field, text, speed="normal")

            self._human_delay(0.3, 0.5)

            # Sync Angular
            self.page.evaluate("""(text) => {
                const f = document.querySelector('textarea[formcontrolname="details"]');
                if (f) {
                    f.dispatchEvent(new Event('input', {bubbles: true}));
                    f.dispatchEvent(new Event('change', {bubbles: true}));
                    f.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
                    f.classList.add('ng-touched', 'ng-dirty', 'ng-valid');
                    f.classList.remove('ng-untouched', 'ng-pristine', 'ng-invalid');
                }
            }""", text)

            self.page.keyboard.press("Tab")
            self._human_delay(0.3, 0.5)
            print("   ✅ Details filled")
            return True
        except Exception as e:
            print("   ❌ Details failed: %s" % str(e))
            try:
                self.page.locator("textarea[formcontrolname='details']").fill(
                    text if text else "Illegal gambling website"
                )
                return True
            except Exception:
                return False

    def select_dropdowns(self):
        try:
            kat = random.choice([
                "Phishing Hiburan", "Phishing Lainnya", "Phishing Retail"
            ])
            print("   🎯 Dropdowns → %s" % kat)

            # Dropdown 1: L1 Taxonomy
            dd1 = self.page.locator("mat-select[formcontrolname='l1Taxonomy']")
            dd1.scroll_into_view_if_needed()
            self._human_delay(0.4, 0.6)
            box = dd1.bounding_box()
            if box:
                self._human_mouse_move(
                    box['x'] + box['width'] / 2,
                    box['y'] + box['height'] / 2
                )
            self._human_delay(0.15, 0.25)
            dd1.click()
            self._human_delay(0.6, 1.0)

            try:
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="visible", timeout=5000
                )
            except Exception:
                dd1.click()
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="visible", timeout=5000
                )

            opt1 = self.page.locator("mat-option").filter(
                has_text="Manipulasi Psikologis"
            ).first
            ob = opt1.bounding_box()
            if ob:
                self._human_mouse_move(
                    ob['x'] + ob['width'] / 2,
                    ob['y'] + ob['height'] / 2, steps=8
                )
            self._human_delay(0.15, 0.25)
            opt1.click()
            print("   ✅ L1: Manipulasi Psikologis")

            self._human_delay(0.5, 0.8)
            try:
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="hidden", timeout=3000
                )
            except Exception:
                self.page.keyboard.press("Escape")
                self._human_delay(0.3, 0.5)

            self._human_delay(0.6, 1.0)

            # Dropdown 2: L3 Taxonomy
            dd2 = self.page.locator("mat-select[formcontrolname='l3Taxonomy']")
            dd2.scroll_into_view_if_needed()
            self._human_delay(0.4, 0.6)
            box = dd2.bounding_box()
            if box:
                self._human_mouse_move(
                    box['x'] + box['width'] / 2,
                    box['y'] + box['height'] / 2
                )
            self._human_delay(0.15, 0.25)
            dd2.click()
            self._human_delay(0.6, 1.0)

            try:
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="visible", timeout=5000
                )
            except Exception:
                dd2.click()
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="visible", timeout=5000
                )

            opt2 = self.page.locator("mat-option").filter(has_text=kat).first
            ob = opt2.bounding_box()
            if ob:
                self._human_mouse_move(
                    ob['x'] + ob['width'] / 2,
                    ob['y'] + ob['height'] / 2, steps=8
                )
            self._human_delay(0.15, 0.25)
            opt2.click()
            print("   ✅ L3: %s" % kat)

            self._human_delay(0.5, 0.7)
            try:
                self.page.wait_for_selector(
                    "div.cdk-overlay-pane mat-option",
                    state="hidden", timeout=3000
                )
            except Exception:
                self.page.keyboard.press("Escape")
                self._human_delay(0.3, 0.5)

            # Sync Angular state
            self.page.evaluate("""() => {
                ['l1Taxonomy', 'l3Taxonomy'].forEach(n => {
                    const el = document.querySelector(
                        'mat-select[formcontrolname="' + n + '"]'
                    );
                    if (el) {
                        el.dispatchEvent(new Event('change', {bubbles: true}));
                        el.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
                        el.classList.remove(
                            'ng-untouched', 'ng-pristine', 'mat-mdc-select-empty'
                        );
                        el.classList.add('ng-touched', 'ng-dirty', 'ng-valid');
                    }
                });
            }""")
            self._human_delay(0.3, 0.4)
            return True
        except Exception as e:
            print("   ❌ Dropdown failed: %s" % str(e))
            return False

    # =========================================================================
    # SUBMIT
    # =========================================================================

    def _verify_page_success(self):
        """Verifikasi dari tampilan halaman apakah submit berhasil."""
        try:
            time.sleep(1.5)

            page_text = ""
            try:
                page_text = self.page.evaluate(
                    "() => document.body ? document.body.innerText.substring(0, 3000) : ''"
                )
            except Exception:
                pass

            text_lower = page_text.lower() if page_text else ""

            # Check error
            error_phrases = [
                'terjadi error', 'coba lagi', 'error occurred',
                'try again', 'something went wrong', 'gagal',
                'failed to submit', 'submission failed',
            ]
            for ep in error_phrases:
                if ep in text_lower:
                    print("   🔍 Page error: '%s'" % ep)
                    return False

            # Check success
            success_phrases = [
                'thank you', 'terima kasih', 'report has been submitted',
                'report received', 'successfully submitted',
                'berhasil dikirim', 'thanks for reporting', 'berhasil',
            ]
            for sp in success_phrases:
                if sp in text_lower:
                    print("   🔍 Page success: '%s'" % sp)
                    return True

            # Check form state
            try:
                form_state = self.page.evaluate("""() => {
                    var u = document.querySelector('input[formcontrolname="url"]');
                    var d = document.querySelector('textarea[formcontrolname="details"]');
                    if (!u && !d) return 'gone';
                    if (u && d && u.value === '' && d.value === '') return 'reset';
                    if (u && u.offsetParent === null) return 'hidden';
                    return 'visible';
                }""")
                if form_state == 'gone':
                    print("   🔍 Form gone = success")
                    return True
                elif form_state == 'visible':
                    print("   🔍 Form still visible = likely failed")
                    return False
            except Exception:
                pass

            print("   🔍 No clear signal = uncertain")
            return False

        except Exception as e:
            print("   🔍 Page verify error: %s" % str(e)[:80])
            return False

    def _soft_reset_form(self):
        """Reset form tanpa reload halaman."""
        try:
            print("   🔄 Soft reset form...")
            url_refill = self._current_url_target
            text_refill = self._current_report_text
            if not url_refill:
                return False

            # Clear fields
            self.page.evaluate("""() => {
                const u = document.querySelector('input[formcontrolname="url"]');
                const d = document.querySelector('textarea[formcontrolname="details"]');
                if (u) {
                    u.value = '';
                    u.dispatchEvent(new Event('input', {bubbles: true}));
                }
                if (d) {
                    d.value = '';
                    d.dispatchEvent(new Event('input', {bubbles: true}));
                }
            }""")
            self._human_delay(1.0, 1.5)

            # Reset tracking
            self._submit_api_status = None
            self._submit_api_response = None
            self._submit_clicked = False
            self._outgoing_post_seen = False

            # Re-fill URL
            f1 = self.page.locator("input[formcontrolname='url']")
            self._human_type(f1, url_refill, speed="fast")
            self.page.keyboard.press("Tab")
            self._human_delay(0.3, 0.5)

            # Re-fill details
            if text_refill:
                f2 = self.page.locator("textarea[formcontrolname='details']")
                f2.click()
                f2.fill(text_refill)
                self.page.evaluate("""() => {
                    const f = document.querySelector('textarea[formcontrolname="details"]');
                    if (f) {
                        f.dispatchEvent(new Event('input', {bubbles: true}));
                        f.dispatchEvent(new Event('change', {bubbles: true}));
                        f.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
                    }
                }""")
                self.page.keyboard.press("Tab")

            self._random_mouse_wander(duration=1.0)
            return True
        except Exception as e:
            print("   ❌ Soft reset failed: %s" % str(e))
            return False

    def intelligent_submit_with_validation_fix(self, max_attempts=3):
        """Submit dengan SUPER AGGRESSIVE warmup dan post-click interaction."""
        try:
            print("📤 Submitting (max %d attempts)..." % max_attempts)

            for attempt in range(max_attempts):
                print("\n   ═══ Attempt %d/%d ═══" % (attempt + 1, max_attempts))

                # Reset state
                self._submit_clicked = False
                self._submit_api_status = None
                self._submit_api_response = None
                self._outgoing_post_seen = False

                # Select dropdowns
                self.select_dropdowns()
                self._human_delay(0.5, 0.8)

                # Verify form
                form_data = self.page.evaluate("""() => {
                    var u = document.querySelector('input[formcontrolname="url"]');
                    var d = document.querySelector('textarea[formcontrolname="details"]');
                    var l1 = document.querySelector('mat-select[formcontrolname="l1Taxonomy"]');
                    var l3 = document.querySelector('mat-select[formcontrolname="l3Taxonomy"]');
                    return {
                        url: u ? u.value.length : 0,
                        det: d ? d.value.length : 0,
                        l1: l1 ? !l1.classList.contains('mat-mdc-select-empty') : false,
                        l3: l3 ? !l3.classList.contains('mat-mdc-select-empty') : false
                    };
                }""")
                print("   Form: URL=%dch Det=%dch L1=%s L3=%s" % (
                    form_data['url'], form_data['det'],
                    form_data['l1'], form_data['l3']
                ))

                if form_data['url'] < 5 or form_data['det'] < 10:
                    print("   ❌ Form incomplete!")
                    if attempt < max_attempts - 1:
                        self._soft_reset_form()
                    continue

                # Force Angular valid
                self.page.evaluate("""() => {
                    document.querySelectorAll(
                        'input[formcontrolname], textarea[formcontrolname], ' +
                        'mat-select[formcontrolname]'
                    ).forEach(f => {
                        f.dispatchEvent(new Event('input', {bubbles: true}));
                        f.dispatchEvent(new Event('change', {bubbles: true}));
                        f.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
                        f.classList.remove('ng-untouched', 'ng-pristine', 'ng-invalid');
                        f.classList.add('ng-touched', 'ng-dirty', 'ng-valid');
                    });
                    const form = document.querySelector('form');
                    if (form) {
                        form.classList.remove('ng-invalid', 'ng-pristine');
                        form.classList.add('ng-valid', 'ng-dirty');
                    }
                    document.querySelectorAll('button').forEach(b => {
                        b.disabled = false;
                        b.removeAttribute('disabled');
                        b.classList.remove('mat-button-disabled');
                    });
                }""")

                # Pre-submit warmup
                print("   🔥 Pre-submit warmup (reCAPTCHA score building)...")
                self._human_scroll("down", random.randint(150, 300))
                self._human_delay(0.8, 1.2)
                self._random_mouse_wander(duration=random.uniform(4.0, 7.0))
                self._human_scroll("up", random.randint(80, 150))
                self._human_delay(0.5, 1.0)

                # Fresh token
                self._force_fresh_recaptcha_token()
                
                # ============================================
                # CRITICAL: LONGER DELAY + POST-TOKEN INTERACTION
                # ============================================
                print("   ⏳ Letting reCAPTCHA settle (CRITICAL)...")
                time.sleep(random.uniform(4.0, 7.0))  # LEBIH LAMA!

                # Tambah interaction SETELAH token
                print("   🎭 Post-token interaction...")
                try:
                    viewport = self.page.viewport_size or {"width": 1280, "height": 720}
                    
                    # Random mouse movements
                    for _ in range(random.randint(3, 5)):
                        x = random.randint(100, viewport["width"] - 100)
                        y = random.randint(100, viewport["height"] - 100)
                        self._human_mouse_move(x, y, steps=random.randint(10, 20))
                        time.sleep(random.uniform(0.5, 1.2))
                    
                    # Small scroll
                    self._human_scroll("down", random.randint(30, 80))
                    time.sleep(random.uniform(0.8, 1.5))
                    self._human_scroll("up", random.randint(20, 50))
                    time.sleep(random.uniform(1.0, 2.0))
                    
                except Exception:
                    pass

                print("   ✅ Ready to submit")

                # Find submit
                submit_btn = None
                for sel in [
                    "button[type='submit']",
                    "button.form-submit-button",
                    "button.mdc-button--raised",
                    "form button.mdc-button",
                    "button:has-text('Submit')",
                    "button:has-text('Kirim')",
                ]:
                    try:
                        btn = self.page.locator(sel).first
                        if btn.is_visible(timeout=1500):
                            submit_btn = btn
                            break
                    except Exception:
                        continue

                if not submit_btn:
                    print("   ❌ No submit button found!")
                    continue

                submit_btn.scroll_into_view_if_needed()
                self._human_delay(0.3, 0.5)

                box = submit_btn.bounding_box()
                if box:
                    self._human_mouse_move(
                        box['x'] + box['width'] / 2 + random.uniform(-3, 3),
                        box['y'] + box['height'] / 2 + random.uniform(-2, 2)
                    )
                self._human_delay(0.5, 0.8)

                # Enable tracking
                self._submit_clicked = True

                # CLICK SUBMIT
                print("   🖱️ Clicking submit...")

                try:
                    with self.page.expect_response(
                        lambda r: ('batchexecute' in r.url.lower() or
                                'safebrowsingsubmission' in r.url.lower()),
                        timeout=30000
                    ) as resp_info:
                        if box:
                            self.page.mouse.click(
                                box['x'] + box['width'] / 2 + random.uniform(-2, 2),
                                box['y'] + box['height'] / 2 + random.uniform(-1, 1)
                            )
                        else:
                            submit_btn.click(timeout=5000)

                    # ============================================
                    # POST-CLICK INTERACTION (HUMAN BEHAVIOR)
                    # ============================================
                    print("   🖱️ Submit clicked, building trust...")
                    time.sleep(random.uniform(0.3, 0.6))

                    try:
                        viewport = self.page.viewport_size or {"width": 1280, "height": 720}
                        
                        # Move mouse away dari button (natural)
                        away_x = random.randint(viewport["width"] // 2, viewport["width"] - 100)
                        away_y = random.randint(200, 400)
                        self._human_mouse_move(away_x, away_y, steps=random.randint(8, 15))
                        time.sleep(random.uniform(0.5, 1.0))
                        
                        # Kadang scroll sedikit (checking result)
                        if random.random() < 0.7:
                            self._human_scroll("down", random.randint(50, 150))
                            time.sleep(random.uniform(0.8, 1.5))
                            
                        # Mouse movement kecil (waiting anxiously)
                        for _ in range(random.randint(2, 4)):
                            x = random.randint(300, viewport["width"] - 300)
                            y = random.randint(200, viewport["height"] - 200)
                            self._human_mouse_move(x, y, steps=random.randint(5, 10))
                            time.sleep(random.uniform(0.3, 0.7))
                            
                    except Exception:
                        pass

                    # Tunggu response lebih lama
                    print("   ⏳ Waiting for response (patient user)...")
                    time.sleep(random.uniform(2.0, 4.0))

                    resp = resp_info.value
                    status = resp.status

                    try:
                        body = resp.text()
                    except Exception:
                        body = ""

                    body_lower = body.lower() if body else ""
                    error_kw = [
                        'generic::internal', 'rpcerror', 'generic::unavailable',
                        'generic::permission', 'generic::invalid',
                    ]
                    has_rpc_error = any(ek in body_lower for ek in error_kw)

                    print("   📡 Response: HTTP %d | RPC_Error=%s" % (
                        status, has_rpc_error))

                    if status == 200 and not has_rpc_error:
                        print("   📡 API clean, verifying page...")
                        time.sleep(3)
                        if self._verify_page_success():
                            print("   ✅✅ SUCCESS CONFIRMED!")
                            return True
                        else:
                            print("   ⚠️ API OK but page unclear")
                            time.sleep(2)
                            if self._verify_page_success():
                                return True

                    elif has_rpc_error:
                        print("   ❌ RPC ERROR detected!")
                        print("   📡 Body: %s" % body[:250])

                        # Recovery
                        if attempt < max_attempts - 1:
                            print("   🔄 Recovery: more interaction...")
                            time.sleep(random.uniform(2, 4))
                            self._random_mouse_wander(
                                duration=random.uniform(5.0, 8.0)
                            )
                            self._human_scroll("down", random.randint(100, 200))
                            time.sleep(random.uniform(1.0, 2.0))
                            self._human_scroll("up", random.randint(50, 150))
                            time.sleep(random.uniform(1.0, 2.0))

                            self._force_fresh_recaptcha_token()
                            time.sleep(random.uniform(2.0, 3.0))

                            self._submit_api_status = None
                            self._submit_api_response = None
                            self._outgoing_post_seen = False
                            self._submit_clicked = True

                            self.page.evaluate("""() => {
                                document.querySelectorAll('button').forEach(b => {
                                    b.disabled = false;
                                    b.removeAttribute('disabled');
                                    b.classList.remove('mat-button-disabled');
                                });
                            }""")

                            print("   🖱️ Re-clicking submit...")
                            try:
                                btn2 = None
                                for sel in ["button[type='submit']",
                                        "button.form-submit-button",
                                        "button.mdc-button--raised"]:
                                    try:
                                        b = self.page.locator(sel).first
                                        if b.is_visible(timeout=2000):
                                            btn2 = b
                                            break
                                    except Exception:
                                        continue

                                if btn2:
                                    btn2.scroll_into_view_if_needed()
                                    self._human_delay(0.3, 0.5)
                                    box2 = btn2.bounding_box()
                                    if box2:
                                        self._human_mouse_move(
                                            box2['x'] + box2['width']/2,
                                            box2['y'] + box2['height']/2
                                        )
                                        self._human_delay(0.3, 0.5)
                                        self.page.mouse.click(
                                            box2['x'] + box2['width']/2,
                                            box2['y'] + box2['height']/2
                                        )

                                    for _ in range(40):
                                        time.sleep(0.5)
                                        if self._submit_api_status is not None:
                                            break

                                    if self._submit_api_status is True:
                                        time.sleep(3)
                                        if self._verify_page_success():
                                            print("   ✅✅ SUCCESS after recovery!")
                                            return True

                            except Exception as re:
                                print("   ⚠️ Recovery click: %s" % str(re)[:80])

                            continue

                    else:
                        print("   ❌ HTTP %d" % status)

                except Exception as e1:
                    err = str(e1).lower()
                    print("   ⚠️ Submit exception: %s" % str(e1)[:150])

                    if 'timeout' in err:
                        if self._outgoing_post_seen:
                            print("   📤 POST was sent, checking result...")
                            time.sleep(5)
                            if self._submit_api_status is True:
                                if self._verify_page_success():
                                    print("   ✅✅ SUCCESS (delayed)")
                                    return True

                        time.sleep(3)
                        if self._verify_page_success():
                            print("   ✅✅ SUCCESS (page verified)")
                            return True

                print("   ❌ Attempt %d failed" % (attempt + 1))
                if attempt < max_attempts - 1:
                    self._soft_reset_form()
                    self._random_mouse_wander(duration=random.uniform(3.0, 5.0))
                    self._human_delay(2.0, 3.0)

            print("❌ All %d attempts failed" % max_attempts)
            return False

        except Exception as e:
            print("❌ Submit error: %s" % str(e))
            import traceback
            traceback.print_exc()
            return False

    def comprehensive_result_check(self):
        if self._submit_api_status is True:
            return self._verify_page_success()
        return False

    # =========================================================================
    # CLOSE
    # =========================================================================

    def close_browser(self):
        try:
            if self.page:
                try:
                    self.page.close()
                except Exception:
                    pass
                self.page = None
                
            if self.context:
                try:
                    self.context.close()
                except Exception:
                    pass
                self.context = None
                
            if self.browser:
                try:
                    self.browser.close()
                except Exception:
                    pass
                self.browser = None
                
            if self.playwright_instance:
                try:
                    self.playwright_instance.stop()
                except Exception:
                    pass
                self.playwright_instance = None
                
        except Exception:
            pass
        self.current_proxy = None

    # =========================================================================
    # MAIN EXECUTION
    # =========================================================================

    def execute_ai_powered_report(self, target_url, content_type=None, keyword=None):
        global proxy_manager

        if proxy_manager.get_total_proxies() == 0:
            print("❌ No proxies available!")
            return False, None

        self._wait_for_rate_limit()

        print("\n" + "=" * 70)
        print("🦊 PLAYWRIGHT FIREFOX STEALTH REPORTER")
        print("=" * 70)
        print("Target: %s" % target_url)
        if content_type:
            print("Content: %s" % content_type)
        if keyword:
            print("Keyword: %s" % keyword)
        print("-" * 70)

        # Pre-generate AI text
        print("\n[PRE] Generating AI text...")
        try:
            if content_type and keyword:
                ai_text = self.ai_generator.generate_custom_report(
                    target_url, content_type, keyword
                )
            else:
                ai_text = self.ai_generator.generate_varied_report(target_url)
            self._cached_ai_text = ai_text
            self._cached_url = target_url
            self._cached_content_type = content_type
            self._cached_keyword = keyword
            print("✅ AI text cached (%d chars)" % len(ai_text))
        except Exception as e:
            print("❌ AI generation failed: %s" % str(e))
            return False, None

        try:
            for proxy_num in range(1, self.max_proxies_to_try + 1):
                try:
                    if proxy_num > 1:
                        d = random.uniform(4, 7)
                        print("\n⏳ Waiting %.1fs before next proxy..." % d)
                        time.sleep(d)

                    print("\n" + "=" * 70)
                    print("🌐 PROXY #%d/%d" % (proxy_num, self.max_proxies_to_try))
                    print("=" * 70)

                    self.use_proxy = True
                    self.current_proxy = None

                    print("\n[1/5] Browser (Playwright Firefox)...")
                    if not self.create_stealth_browser():
                        self.close_browser()
                        continue

                    print("\n[2/5] Navigate...")
                    if not self.natural_navigation():
                        proxy_manager.mark_proxy_failed(self.current_proxy)
                        self.close_browser()
                        continue

                    self._human_delay(0.5, 1.0)

                    print("\n[3/5] URL...")
                    if not self.ai_powered_paste_url(target_url):
                        self.close_browser()
                        continue

                    self._human_delay(0.5, 1.0)

                    print("\n[4/5] Details...")
                    if not self.ai_powered_paste_details(
                        target_url, content_type, keyword
                    ):
                        self.close_browser()
                        continue

                    self._human_delay(0.5, 1.0)

                    print("\n[5/5] Submit...")
                    if self.intelligent_submit_with_validation_fix(
                        self.max_submit_retries_per_proxy
                    ):
                        print("\n" + "=" * 50)
                        print("✅ REPORT SUBMITTED SUCCESSFULLY!")
                        print("🦊 Via Playwright Firefox + %s:%s" % (
                            self.current_proxy['host'],
                            self.current_proxy['port']
                        ))
                        print("=" * 50)
                        proxy_manager.mark_proxy_success(self.current_proxy)

                        wait_sec = 10
                        print("⏳ Verifying %ds..." % wait_sec)
                        time.sleep(wait_sec)

                        self.close_browser()
                        return True, None
                    else:
                        print("\n❌ Submit failed with this proxy")
                        if self.current_proxy:
                            proxy_manager.mark_proxy_failed(self.current_proxy)
                        self.close_browser()
                        continue

                except Exception as e:
                    print("\n❌ Proxy attempt error: %s" % str(e))
                    import traceback
                    traceback.print_exc()
                    if self.current_proxy:
                        proxy_manager.mark_proxy_failed(self.current_proxy)
                    self.close_browser()
                    continue
        finally:
            self.close_browser()
            self._cached_ai_text = None
            self._cached_url = None
            self._cached_content_type = None
            self._cached_keyword = None

        print("\n❌ FAILED after %d proxies" % self.max_proxies_to_try)
        return False, None

class EnhancedTelegramBot:
    def __init__(self, config: Config):
        self.config = config
        self.token = config.get_token()
        self.owner_id = config.get_owner_id()
        self.ai_api_url = config.get_ai_api_url()
        self.ai_generator = AIReportGenerator(self.ai_api_url)
        self.report_tracker = ReportTracker(
            daily_limit=config.get_daily_limit(),
            re_report_cooldown_minutes=config.get_re_report_cooldown()
        )
        self.ua_manager = UserAgentManager()
        self.schedule_manager = None
        self.proxy_auto_updater = None
        max_workers = config.get_max_concurrent_reports()
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
        print(f"🔧 Initialized ThreadPool with {max_workers} max concurrent reports")
        self.user_access: Dict[int, UserAccess] = {
            self.owner_id: UserAccess(self.owner_id)
        }
        self.data_file = "bot_data.json"
        self.load_data()

    def load_data(self):
        try:
            if os.path.exists(self.data_file):
                with open(self.data_file, "r") as f:
                    data = json.load(f)
                    for user_data in data.get("user_access", []):
                        access = UserAccess.from_dict(user_data)
                        self.user_access[access.user_id] = access
        except Exception as e:
            logger.error(f"Data load failed: {e}")

    def save_data(self):
        try:
            data = {
                "user_access": [access.to_dict() for access in self.user_access.values()]
            }
            with open(self.data_file, "w") as f:
                json.dump(data, f, indent=2)
        except Exception as e:
            logger.error(f"Data save failed: {e}")

    def is_authorized(self, user_id: int) -> bool:
        if user_id not in self.user_access:
            return False
        access = self.user_access[user_id]
        if access.is_expired():
            del self.user_access[user_id]
            self.save_data()
            return False
        return True

    def is_owner(self, user_id: int) -> bool:
        return user_id == self.owner_id

    def normalize_url(self, url: str) -> str:
        url = url.strip()
        if not (url.startswith("http://") or url.startswith("https://")):
            url = "https://" + url
        return url

    def add_user(self, user_id: int, permanent: bool = False, hours: int = None) -> UserAccess:
        expires_at = None
        if not permanent and hours is not None:
            expires_at = datetime.now() + timedelta(hours=hours)
        access = UserAccess(user_id, expires_at)
        self.user_access[user_id] = access
        self.save_data()
        return access

    def remove_user(self, user_id: int) -> bool:
        if user_id in self.user_access and user_id != self.owner_id:
            del self.user_access[user_id]
            self.save_data()
            return True
        return False

    def list_users(self) -> List[Dict]:
        users = []
        for user_id, access in self.user_access.items():
            users.append({
                "user_id": user_id,
                "is_owner": user_id == self.owner_id,
                "is_permanent": access.is_permanent,
                "expires_at": access.expires_at,
                "is_expired": access.is_expired(),
                "remaining": access.get_remaining_time()
            })
        return users

class EnhancedBotHandlers:
    def __init__(self, bot: EnhancedTelegramBot):
        self.bot = bot
        self.pending_reports: Dict[int, Dict] = {}

    async def reloadproxy_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            await update.message.reply_text("🚫 Admin command only")
            return
        status_msg = await update.message.reply_text(
            "🔄 *RELOADING PROXY LIST*\n\n"
            "⏳ Fetching from URL...\n"
            "⏱️ Timeout: 30 seconds",
            parse_mode=ParseMode.MARKDOWN
        )
        try:
            success, message, new_count = await asyncio.wait_for(
                asyncio.to_thread(proxy_manager.reload_proxies_from_url),
                timeout=30.0
            )
            if success:
                protocol_count = {}
                for proxy in proxy_manager.proxy_list:
                    protocol = proxy.get("protocol", "http").upper()
                    protocol_count[protocol] = protocol_count.get(protocol, 0) + 1
                protocol_summary = "\n".join([
                    f"  • {protocol}: {count} proxies"
                    for protocol, count in protocol_count.items()
                ])
                await status_msg.edit_text(
                    f"✅ *PROXY RELOAD SUCCESSFUL*\n\n"
                    f"📊 *Summary:*\n{message}\n\n"
                    f"🌐 *Protocol Distribution:*\n{protocol_summary}\n\n"
                    f"🕒 *Last Updated:* {proxy_manager.get_last_update_info()}",
                    parse_mode=ParseMode.MARKDOWN
                )
            else:
                await status_msg.edit_text(
                    f"❌ *PROXY RELOAD FAILED*\n\n"
                    f"⚠️ Error: {message}\n\n"
                    f"Current proxy count: {new_count}",
                    parse_mode=ParseMode.MARKDOWN
                )
        except asyncio.TimeoutError:
            await status_msg.edit_text(
                f"❌ *RELOAD TIMEOUT*\n\n"
                f"⚠️ Operation exceeded 30 seconds\n\n"
                f"Current proxy count: {proxy_manager.get_total_proxies()}\n\n"
                f"💡 Try again or check proxy URL",
                parse_mode=ParseMode.MARKDOWN
            )
        except Exception as e:
            await status_msg.edit_text(
                f"❌ *RELOAD ERROR*\n\n"
                f"Error: {str(e)}\n\n"
                f"Current proxy count: {proxy_manager.get_total_proxies()}",
                parse_mode=ParseMode.MARKDOWN
            )

    async def proxystats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            await update.message.reply_text("🚫 Admin command only")
            return
        stats = proxy_manager.get_stats()
        mode = self.bot.config.get_proxy_mode()
        protocol_count = {}
        for proxy in PROXY_LIST:
            protocol = proxy.get("protocol", "http").upper()
            protocol_count[protocol] = protocol_count.get(protocol, 0) + 1
        min_minutes = self.bot.config.get_min_schedule_interval()
        msg = f"🌐 *PROXY & SMART SWITCHING CONFIG*\n\n"
        msg += f"✅ *Bot:* 🟢 Always Responsive\n\n"
        msg += "*Protocol Distribution:*\n"
        for protocol, count in protocol_count.items():
            msg += f"  • {protocol}: {count} proxies\n"
        msg += f"\n💡 Use /reloadproxy to manually refresh\n\n"
        msg += f"{stats}"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            await update.message.reply_text(
                f"🚫 *ACCESS DENIED*\n\n"
                f"👤 Your User ID: `{user_id}`\n\n"
                f"📧 Contact admin for access. @AXVDIGITAL",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        name = update.effective_user.first_name
        stats = self.bot.report_tracker.get_user_stats(user_id)
        min_minutes = self.bot.config.get_min_schedule_interval()
        msg = f"""👋 *Welcome, {name}!*
📊 *Daily Limit:*
▫️ {stats['unique_urls_today']}/{stats['daily_limit']} NEW URLs today
▫️ {stats['total_unique_urls']} total URLs tracked

*📋 Available Commands:*
/report `<url>` - Report dengan custom content
/schedule `<url>` `<minutes>` - Schedule dengan custom content
/quickreport `<url>` - Report cepat (auto content)
/quickschedule `<url>` `<minutes>` - Schedule cepat (auto content)
/schedules - View your schedules active
/stats - Your statistics
/cancel - Cancel current operation
/help - Help menu"""
        if self.bot.is_owner(user_id):
            msg += "\n\n*👑 Admin Commands:*\n/add /remove /list /reset"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

    async def report_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            await update.message.reply_text("🚫 Access denied")
            return ConversationHandler.END
        if not context.args:
            await update.message.reply_text(
                "📝 *CUSTOM REPORT*\n\n"
                "Usage: `/report <url>`\n\n"
                "Contoh: `/report https://example.com`\n\n"
                "Ketik /cancel untuk membatalkan",
                parse_mode=ParseMode.MARKDOWN
            )
            return ConversationHandler.END
        url = self.bot.normalize_url(context.args[0])
        can_report, reason = self.bot.report_tracker.can_report(user_id, url)
        if not can_report:
            await update.message.reply_text(
                f"❌ *CANNOT REPORT*\n\n"
                f"🎯 *URL:* `{url}`\n\n"
                f"{reason}",
                parse_mode=ParseMode.MARKDOWN
            )
            return ConversationHandler.END
        # Auto-fill content_type dengan "judi online"
        self.pending_reports[user_id] = {
            "url": url,
            "content_type": "judi online",  # AUTO-FILLED
            "keyword": None,
            "schedule_minutes": None,
            "is_schedule": False
        }
        is_rereport = "RE-REPORT" in reason
        status_text = "🔄 *Re-report*" if is_rereport else "🆕 *New URL*"
        await update.message.reply_text(
            f"✅ *URL READY TO REPORT*\n\n"
            f"🎯 *URL:* `{url}`\n"
            f"📊 *Status:* {status_text}\n"
            # f"📝 *Jenis Konten:* `judi online` (auto)\n\n"
            f"━━━━━━━━━━━━━━━━━━━━━━\n"
            f"📌 Keyword apa yang digunakan untuk menemukan situs ini di Google ❓\n\n"
            f"Contoh:\n"
            f"• `slot gacor`\n"
            f"• `togel online`\n"
            f"Ketik /cancel untuk membatalkan",
            parse_mode=ParseMode.MARKDOWN
        )
        return WAITING_KEYWORD  # Langsung ke keyword, skip content

    async def schedule_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            await update.message.reply_text("🚫 Access denied")
            return ConversationHandler.END
        min_minutes = self.bot.config.get_min_schedule_interval()
        max_schedules = self.bot.config.get_max_schedules_per_user()
        if len(context.args) < 2:
            await update.message.reply_text(
                f"📅 *CUSTOM SCHEDULE*\n\n"
                f"Usage: `/schedule <url> <minutes>`\n\n"
                f"Contoh: `/schedule https://xxx.com 30`\n\n"
                f"⚠️ *Limits:*\n"
                f"• Minimum: {min_minutes} minutes\n"
                f"• Max {max_schedules} active schedules\n\n"
                f"Ketik /cancel untuk membatalkan",
                parse_mode=ParseMode.MARKDOWN
            )
            return ConversationHandler.END
        url = self.bot.normalize_url(context.args[0])
        try:
            schedule_minutes = int(context.args[1])
            if schedule_minutes < min_minutes or schedule_minutes > 10080:
                await update.message.reply_text(
                    f"⚠️ Invalid interval\n\n"
                    f"Minimum: {min_minutes} minutes\n"
                    f"Examples: 10, 30, 60, 120"
                )
                return ConversationHandler.END
        except ValueError:
            await update.message.reply_text(
                "⚠️ Invalid format\n\n"
                "Use: /schedule <url> <minutes>\n"
                "Examples: 10, 30, 60, 120"
            )
            return ConversationHandler.END
        user_active_schedules = len(await self.bot.schedule_manager.get_user_schedules(user_id))
        if user_active_schedules >= max_schedules:
            await update.message.reply_text(
                f"❌ *SCHEDULE LIMIT REACHED*\n\n"
                f"📊 *Active schedules:* {user_active_schedules}/{max_schedules}\n\n"
                f"💡 Use `/stop <url>` to remove a schedule first",
                parse_mode=ParseMode.MARKDOWN
            )
            return ConversationHandler.END
        existing_schedules = await self.bot.schedule_manager.get_user_schedules(user_id)
        for schedule in existing_schedules:
            if self.bot.report_tracker.normalize_url(schedule.url) == self.bot.report_tracker.normalize_url(url):
                interval_minutes = int(schedule.interval_hours * 60)
                await update.message.reply_text(
                    f"❌ *URL ALREADY SCHEDULED*\n\n"
                    f"🎯 *URL:* `{url}`\n"
                    f"⏰ *Current interval:* Every {interval_minutes} minutes\n\n"
                    f"💡 Use `/stop {url}` to remove it first",
                    parse_mode=ParseMode.MARKDOWN
                )
                return ConversationHandler.END
        # Auto-fill content_type dengan "judi online"
        self.pending_reports[user_id] = {
            "url": url,
            "content_type": "judi online",  # AUTO-FILLED
            "keyword": None,
            "schedule_minutes": schedule_minutes,
            "is_schedule": True
        }
        await update.message.reply_text(
            f"✅ *SCHEDULE READY*\n\n"
            f"🎯 *URL:* `{url}`\n"
            f"⏰ *Interval:* Every {schedule_minutes} minutes\n"
            f"📊 *Your schedules:* {user_active_schedules}/{max_schedules}\n"
            # f"📝 *Jenis Konten:* `judi online` (auto)\n\n"
            f"━━━━━━━━━━━━━━━━━━━━━━\n\n"
            f"📌 Keyword apa yang digunakan untuk menemukan situs ini di Google ❓\n\n"
            f"Contoh:\n"
            f"• `slot gacor`\n"
            f"• `togel online`\n"
            f"Ketik /cancel untuk membatalkan",
            parse_mode=ParseMode.MARKDOWN
        )
        return WAITING_SCHEDULE_KEYWORD  # Langsung ke keyword, skip content

    async def receive_schedule_content(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        content_type = update.message.text.strip()
        if user_id not in self.pending_reports:
            await update.message.reply_text("❌ Session expired. Please start again with /schedule <url> <minutes>")
            return ConversationHandler.END
        self.pending_reports[user_id]["content_type"] = content_type
        await update.message.reply_text(
            f"📌 Keyword apa yang digunakan untuk menemukan situs ini di Google ❓\n\n"
            f"Contoh:\n"
            f"• `slot gacor`\n"
            f"Ketik /cancel untuk membatalkan",
            parse_mode=ParseMode.MARKDOWN
        )
        return WAITING_SCHEDULE_KEYWORD

    async def receive_schedule_keyword(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        keyword = update.message.text.strip()
        if user_id not in self.pending_reports:
            await update.message.reply_text("❌ Session expired. Please start again with /schedule <url> <minutes>")
            return ConversationHandler.END
        self.pending_reports[user_id]["keyword"] = keyword
        report_data = self.pending_reports[user_id]
        url = report_data["url"]
        content_type = report_data["content_type"]
        schedule_minutes = report_data["schedule_minutes"]
        del self.pending_reports[user_id]
        success, message = await self.bot.schedule_manager.add_schedule(
            user_id, url, schedule_minutes, content_type, keyword
        )
        if success:
            active_schedules = len(await self.bot.schedule_manager.get_user_schedules(user_id))
            await update.message.reply_text(
                f"✅ *SCHEDULE CREATED WITH CUSTOM CONTENT*\n\n"
                f"🎯 *Target:* `{url}`\n"
                f"📝 *Content:* `{content_type}`\n"
                f"🔍 *Keyword:* `{keyword}`\n"
                f"⏰ *Interval:* Every {schedule_minutes} minutes\n"
                f"📋 *Your schedules:* {active_schedules}\n\n"
                f"⏳ Submitting first report now...",
                parse_mode=ParseMode.MARKDOWN
            )
            can_report, reason = self.bot.report_tracker.can_report(user_id, url)
            if can_report:
                asyncio.create_task(
                    self._execute_custom_report_background(update, url, content_type, keyword, user_id, is_schedule=True)
                )
            else:
                next_run = datetime.now() + timedelta(minutes=schedule_minutes)
                await update.message.reply_text(
                    f"ℹ️ *First Report:* {reason}\n"
                    f"Schedule will run at: {next_run.strftime('%Y-%m-%d %H:%M:%S')}",
                    parse_mode=ParseMode.MARKDOWN
                )
        else:
            await update.message.reply_text(
                f"❌ *SCHEDULE FAILED*\n\n{message}",
                parse_mode=ParseMode.MARKDOWN
            )
        return ConversationHandler.END

    async def receive_content(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        content_type = update.message.text.strip()
        if user_id not in self.pending_reports:
            await update.message.reply_text("❌ Session expired. Please start again with /report <url>")
            return ConversationHandler.END
        self.pending_reports[user_id]["content_type"] = content_type
        await update.message.reply_text(
            # f"✅ *Jenis Konten:* `{content_type}`\n\n"
            f"📌 Keyword apa yang digunakan untuk menemukan situs ini di Google ❓\n\n"
            f"Contoh:\n"
            f"• `slot gacor`\n"
            f"Ketik /cancel untuk membatalkan",
            parse_mode=ParseMode.MARKDOWN
        )
        return WAITING_KEYWORD

    async def receive_keyword(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        keyword = update.message.text.strip()
        if user_id not in self.pending_reports:
            await update.message.reply_text("❌ Session expired. Please start again with /report <url>")
            return ConversationHandler.END
        self.pending_reports[user_id]["keyword"] = keyword
        report_data = self.pending_reports[user_id]
        url = report_data["url"]
        content_type = report_data["content_type"]
        del self.pending_reports[user_id]
        asyncio.create_task(
            self._execute_custom_report_background(update, url, content_type, keyword, user_id)
        )
        return ConversationHandler.END

    async def cancel_conversation(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if user_id in self.pending_reports:
            del self.pending_reports[user_id]
        await update.message.reply_text(
            "❌ *Operasi dibatalkan*\n\n"
            "Gunakan /report <url> atau /schedule <url> <minutes> untuk memulai lagi",
            parse_mode=ParseMode.MARKDOWN
        )
        return ConversationHandler.END

    async def _execute_custom_report_background(self, update, url, content_type, keyword, user_id, is_schedule=False):
        status_msg = None
        try:
            can_report, reason = self.bot.report_tracker.can_report(user_id, url)
            if not can_report:
                await update.message.reply_text(
                    f"❌ *CANNOT REPORT*\n\n{reason}",
                    parse_mode=ParseMode.MARKDOWN
                )
                return
            is_rereport = "RE-REPORT" in reason
            status_msg = await update.message.reply_text(
                f"🔄 *PROCESSING REPORT...*\n\n"
                f"🎯 *Target:* `{url}`\n"
                f"📝 *Content:* `{content_type}`\n"
                f"🔍 *Keyword:* `{keyword}`\n"
                f"🔄 *Type:* {'Re-report' if is_rereport else 'New URL'}\n\n"
                f"⏳ Generating AI report text...",
                parse_mode=ParseMode.MARKDOWN
            )
            user_agent = self.bot.ua_manager.get_random_user_agent(user_id)
            print(f"⚡ Submitting custom report to background thread: {url}")
            success, _ = await asyncio.to_thread(
                self._blocking_custom_report_execution,
                url,
                content_type,
                keyword,
                user_agent
            )
            if status_msg:
                try:
                    await status_msg.delete()
                except Exception as e:
                    print(f"⚠️ Could not delete status message: {e}")
            await self.bot.schedule_manager.update_schedule_after_manual_report(user_id, url, success)
            if success:
                self.bot.report_tracker.add_report(user_id, url)
                stats = self.bot.report_tracker.get_user_stats(user_id)
                success_image_path = self.bot.config.get_success_image_path()
                if os.path.exists(success_image_path):
                    try:
                        with open(success_image_path, "rb") as photo:
                            await update.message.reply_photo(
                                photo=photo,
                                caption=f"✅ *REPORT SUCCESSFUL ✨*\n\n"
                                        f"🎯 *Target:* `{url}`\n"
                                        f"📝 *Content:* `{content_type}`\n"
                                        f"🔍 *Keyword:* `{keyword}`\n\n"
                                        f"♻️ *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                                parse_mode=ParseMode.MARKDOWN
                            )
                    except Exception as e:
                        print(f"Failed to send success image: {e}")
                        await update.message.reply_text(
                            f"✅ *REPORT SUCCESSFUL ✨*\n\n"
                            f"🎯 *Target:* `{url}`\n"
                            f"📝 *Content:* `{content_type}`\n"
                            f"🔍 *Keyword:* `{keyword}`\n\n"
                            f"♻️ *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                            parse_mode=ParseMode.MARKDOWN
                        )
                else:
                    await update.message.reply_text(
                        f"✅ *REPORT SUCCESSFUL ✨*\n\n"
                        f"🎯 *Target:* `{url}`\n"
                        f"📝 *Content:* `{content_type}`\n"
                        f"🔍 *Keyword:* `{keyword}`\n"
                        f"📊 *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                        parse_mode=ParseMode.MARKDOWN
                    )
            else:
                await update.message.reply_text(
                    f"❌ *REPORT FAILED*\n\n"
                    f"🎯 *Target:* `{url}`\n\n"
                    f"💡 Retry: `/report {url}`",
                    parse_mode=ParseMode.MARKDOWN
                )
        except Exception as e:
            print(f"Background custom report error: {e}")

            import traceback

            traceback.print_exc()
            if status_msg:
                try:
                    await status_msg.delete()
                except:
                    pass
            await update.message.reply_text(
                f"❌ *REPORT ERROR*\n\n"
                f"🎯 *Target:* `{url}`\n"
                f"⚠️ Error: {str(e)[:100]}",
                parse_mode=ParseMode.MARKDOWN
            )

    def _blocking_custom_report_execution(self, url, content_type, keyword, user_agent):
        try:
            reporter = EnhancedUltimateStealth(
                self.bot.ai_generator,
                user_agent,
                self.bot.config.get_proxy_mode(),
                self.bot.config.get_max_proxies_to_try(),
                self.bot.config.get_max_submit_retries_per_proxy()
            )
            result = reporter.execute_ai_powered_report(url, content_type, keyword)
            print(f"✅ Thread completed custom report: {url}")
            return result
        except Exception as e:
            print(f"❌ Thread execution error for {url}: {e}")

            import traceback

            traceback.print_exc()
            return False, None

    async def quick_report_url(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            await update.message.reply_text("🚫 Access denied")
            return
        if not context.args:
            await update.message.reply_text(
                "📝 *Quick Report Usage:*\n\n"
                "▫️ `/quickreport <url>` - Single report (auto content)\n\n"
                f"💡 Use `/report <url>` for custom content",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        url = self.bot.normalize_url(context.args[0])
        asyncio.create_task(
            self._execute_report_background(update, url, user_id, is_schedule=False)
        )

    async def quick_schedule_url(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            await update.message.reply_text("🚫 Access denied")
            return
        max_schedules = self.bot.config.get_max_schedules_per_user()
        min_minutes = self.bot.config.get_min_schedule_interval()
        if len(context.args) < 2:
            await update.message.reply_text(
                "📝 *Quick Schedule Usage:*\n\n"
                "▫️ `/quickschedule <url> <minutes>` - Schedule (auto content)\n\n"
                "⏰ *Examples:*\n"
                "   • `/quickschedule url 10` - Auto report every 10 minutes\n\n"
                f"⚠️ *Limits:*\n"
                f"   • Minimum: {min_minutes} minutes\n"
                f"   • Max {max_schedules} active schedules\n\n"
                f"💡 Use `/schedule <url> <minutes>` for custom content",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        url = self.bot.normalize_url(context.args[0])
        try:
            schedule_minutes = int(context.args[1])
            if schedule_minutes < min_minutes or schedule_minutes > 10080:
                await update.message.reply_text(
                    f"⚠️ Invalid interval\n\n"
                    f"Minimum: {min_minutes} minutes\n"
                    f"Examples: 10, 30, 60, 120"
                )
                return
        except ValueError:
            await update.message.reply_text(
                "⚠️ Invalid format\n\n"
                "Use: /quickschedule <url> <minutes>\n"
                "Examples: 10, 30, 60, 120"
            )
            return
        success, message = await self.bot.schedule_manager.add_schedule(
            user_id, url, schedule_minutes, None, None
        )
        if success:
            active_schedules = len(await self.bot.schedule_manager.get_user_schedules(user_id))
            can_report, reason = self.bot.report_tracker.can_report(user_id, url)
            if can_report:
                await update.message.reply_text(
                    f"✅ *QUICK SCHEDULE CREATED*\n\n"
                    f"🎯 *Target:* `{url}`\n"
                    f"⏰ *Interval:* Every {schedule_minutes} minutes\n"
                    f"📝 *Content:* Auto-generated\n"
                    f"📋 *Your schedules:* {active_schedules}\n\n"
                    f"Submitting first report now...",
                    parse_mode=ParseMode.MARKDOWN
                )
                asyncio.create_task(
                    self._execute_report_background(update, url, user_id, is_schedule=True)
                )
            else:
                next_run = datetime.now() + timedelta(minutes=schedule_minutes)
                await update.message.reply_text(
                    f"✅ *QUICK SCHEDULE CREATED*\n\n"
                    f"🎯 *Target:* `{url}`\n"
                    f"⏰ *Interval:* Every {schedule_minutes} minutes\n"
                    f"📝 *Content:* Auto-generated\n"
                    f"📋 *Your schedules:* {active_schedules}\n\n"
                    f"ℹ️ *First Report:* {reason}\n"
                    f"Schedule will run at: {next_run.strftime('%Y-%m-%d %H:%M:%S')}",
                    parse_mode=ParseMode.MARKDOWN
                )
        else:
            await update.message.reply_text(
                f"❌ *SCHEDULE FAILED*\n\n{message}",
                parse_mode=ParseMode.MARKDOWN
            )

    async def _execute_report_background(self, update, url, user_id, is_schedule=False):
        status_msg = None
        try:
            can_report, reason = self.bot.report_tracker.can_report(user_id, url)
            if not can_report:
                await update.message.reply_text(
                    f"❌ *CANNOT REPORT*\n\n{reason}",
                    parse_mode=ParseMode.MARKDOWN
                )
                return
            is_rereport = "RE-REPORT" in reason
            status_msg = await update.message.reply_text(
                f"🔄 *PROCESSING QUICK REPORT...*\n\n"
                f"🎯 *Target:* `{url}`\n"
                f"🔄 *Type:* {'Re-report' if is_rereport else 'New URL'}\n"
                f"📝 *Content:* Auto-generating...",
                parse_mode=ParseMode.MARKDOWN
            )
            user_agent = self.bot.ua_manager.get_random_user_agent(user_id)
            print(f"⚡ Submitting quick report to background thread: {url}")
            success, _ = await asyncio.to_thread(
                self._blocking_report_execution,
                url,
                user_agent
            )
            if status_msg:
                try:
                    await status_msg.delete()
                except Exception as e:
                    print(f"⚠️ Could not delete status message: {e}")
            await self.bot.schedule_manager.update_schedule_after_manual_report(user_id, url, success)
            if success:
                self.bot.report_tracker.add_report(user_id, url)
                stats = self.bot.report_tracker.get_user_stats(user_id)
                success_image_path = self.bot.config.get_success_image_path()
                if os.path.exists(success_image_path):
                    try:
                        with open(success_image_path, "rb") as photo:
                            await update.message.reply_photo(
                                photo=photo,
                                caption=f"✅ *QUICK REPORT SUCCESSFUL!*\n\n"
                                        f"🎯 *Target:* `{url}`\n"
                                        f"📊 *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                                parse_mode=ParseMode.MARKDOWN
                            )
                    except Exception as e:
                        print(f"Failed to send success image: {e}")
                        await update.message.reply_text(
                            f"✅ *QUICK REPORT SUCCESSFUL!*\n\n"
                            f"🎯 *Target:* `{url}`\n"
                            f"📊 *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                            parse_mode=ParseMode.MARKDOWN
                        )
                else:
                    await update.message.reply_text(
                        f"✅ *QUICK REPORT SUCCESSFUL!*\n\n"
                        f"🎯 *Target:* `{url}`\n"
                        f"📊 *Daily Status:* {stats['unique_urls_today']}/{stats['daily_limit']} unique URLs",
                        parse_mode=ParseMode.MARKDOWN
                    )
            else:
                await update.message.reply_text(
                    f"❌ *QUICK REPORT FAILED*\n\n"
                    f"🎯 *Target:* `{url}`\n\n"
                    f"💡 Retry: `/quickreport {url}`",
                    parse_mode=ParseMode.MARKDOWN
                )
        except Exception as e:
            print(f"Background report error: {e}")

            import traceback

            traceback.print_exc()
            if status_msg:
                try:
                    await status_msg.delete()
                except:
                    pass
            await update.message.reply_text(
                f"❌ *REPORT ERROR*\n\n"
                f"🎯 *Target:* `{url}`\n"
                f"⚠️ Error: {str(e)[:100]}",
                parse_mode=ParseMode.MARKDOWN
            )

    def _blocking_report_execution(self, url, user_agent):
        try:
            print(f"🔧 Thread executing report: {url}")
            reporter = EnhancedUltimateStealth(
                self.bot.ai_generator,
                user_agent,
                self.bot.config.get_proxy_mode(),
                self.bot.config.get_max_proxies_to_try(),
                self.bot.config.get_max_submit_retries_per_proxy()
            )
            result = reporter.execute_ai_powered_report(url)
            print(f"✅ Thread completed report: {url}")
            return result
        except Exception as e:
            print(f"❌ Thread execution error for {url}: {e}")

            import traceback

            traceback.print_exc()
            return False, None

    async def status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            return
        user_stats = self.bot.report_tracker.get_user_stats(user_id)
        active_schedules = len(await self.bot.schedule_manager.get_user_schedules(user_id))
        msg = f"*👤 Your Statistics:*\n"
        msg += f"▫️ Today: {user_stats['unique_urls_today']}/{user_stats['daily_limit']} URLs\n"
        msg += f"▫️ Remaining: {user_stats['remaining_today']} URLs\n"
        msg += f"▫️ All Time: {user_stats['total_unique_urls']} URLs\n"
        msg += f"▫️ Active Schedules: {active_schedules}\n\n"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

    async def schedules_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            return
        schedules = await self.bot.schedule_manager.get_user_schedules(user_id)
        max_schedules = self.bot.config.get_max_schedules_per_user()
        min_minutes = self.bot.config.get_min_schedule_interval()
        if not schedules:
            await update.message.reply_text(
                "📭 *No Active Schedules*\n\n"
                f"Use `/schedule <url> <minutes>` for custom content\n"
                f"Or `/quickschedule <url> <minutes>` for auto content\n\n"
                f"💡 Max {max_schedules} schedules active",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        msg = f"📅 *YOUR SCHEDULES* ({len(schedules)}/{max_schedules} active)\n\n"
        for i, schedule in enumerate(schedules, 1):
            interval_minutes = int(schedule.interval_hours * 60)
            content_info = f"📝 {schedule.content_type}" if schedule.content_type else "📝 Auto-generated"
            keyword_info = f"🔍 {schedule.keyword}" if schedule.keyword else ""
            msg += f"*{i}.* `{schedule.url}`\n"
            msg += f"   ⏰ Every {interval_minutes} minutes\n"
            msg += f"   {schedule.get_time_until_next_run()}\n"
            msg += f"   {content_info}\n"
            if keyword_info:
                msg += f"   {keyword_info}\n"
            msg += f"   📊 Success: {schedule.successful_runs}/{schedule.total_runs}\n\n"
        msg += "\n💡 *Tip:* Use `/stop <url>` to stop a schedule"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

    async def stop_schedule_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            return
        if not context.args:
            await update.message.reply_text(
                "📝 *Usage:* `/stop <url>`",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        url = self.bot.normalize_url(context.args[0])
        if await self.bot.schedule_manager.remove_schedule(user_id, url):
            remaining = len(await self.bot.schedule_manager.get_user_schedules(user_id))
            await update.message.reply_text(
                f"✅ *Schedule Stopped*\n\n"
                f"`{url}`\n\n"
                f"📋 Remaining schedules: {remaining}",
                parse_mode=ParseMode.MARKDOWN
            )
        else:
            await update.message.reply_text(
                f"❌ No schedule found for:\n`{url}`",
                parse_mode=ParseMode.MARKDOWN
            )

    async def stopall_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_authorized(user_id):
            return
        count = await self.bot.schedule_manager.clear_user_schedules(user_id)
        if count > 0:
            await update.message.reply_text(
                f"✅ *All Schedules Stopped*\n\n"
                f"Stopped {count} schedule(s)",
                parse_mode=ParseMode.MARKDOWN
            )
        else:
            await update.message.reply_text("📭 No active schedules to stop")

    async def stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        await self.status_command(update, context)

    async def add_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            return
        if not context.args:
            await update.message.reply_text(
                "📝 *Usage:*\n\n"
                "▫️ `/add <user_id>` - Permanent access\n"
                "▫️ `/add <user_id> <hours>` - Temporary access",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        try:
            new_id = int(context.args[0])
            hours = int(context.args[1]) if len(context.args) > 1 else None
            permanent = hours is None
            access = self.bot.add_user(new_id, permanent=permanent, hours=hours)
            await update.message.reply_text(
                f"✅ *USER ADDED*\n\n"
                f"👤 *ID:* `{new_id}`\n"
                f"⏰ *Access:* {access.get_remaining_time()}",
                parse_mode=ParseMode.MARKDOWN
            )
        except:
            await update.message.reply_text("❌ Invalid format")

    async def remove_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            return
        if not context.args:
            await update.message.reply_text(
                "📝 *Usage:* `/remove <user_id>`",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        try:
            remove_id = int(context.args[0])
            if self.bot.remove_user(remove_id):
                await update.message.reply_text(
                    f"✅ *USER REMOVED*\n\n"
                    f"👤 *ID:* `{remove_id}`",
                    parse_mode=ParseMode.MARKDOWN
                )
            else:
                await update.message.reply_text("❌ User not found or cannot be removed")
        except:
            await update.message.reply_text("❌ Invalid format")

    async def list_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            return
        users = self.bot.list_users()
        if not users:
            await update.message.reply_text("📭 No users found")
            return
        msg = "👥 *USER LIST*\n\n"
        for user in users:
            role = "👑 OWNER" if user["is_owner"] else "👤 USER"
            msg += f"*{user['user_id']}* - {role}\n"
            msg += f"   {user['remaining']}\n\n"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

    async def reset_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        if not self.bot.is_owner(user_id):
            return
        if not context.args:
            await update.message.reply_text(
                "📝 *Usage:* `/reset <user_id>`",
                parse_mode=ParseMode.MARKDOWN
            )
            return
        try:
            reset_id = int(context.args[0])
            if self.bot.report_tracker.reset_user_reports(reset_id):
                await update.message.reply_text(
                    f"✅ *REPORTS RESET*\n\n"
                    f"👤 *User ID:* `{reset_id}`\n"
                    f"All report history and cooldowns cleared",
                    parse_mode=ParseMode.MARKDOWN
                )
            else:
                await update.message.reply_text("❌ User not found")
        except:
            await update.message.reply_text("❌ Invalid format")

    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        user_id = update.effective_user.id
        is_owner = self.bot.is_owner(user_id)
        min_minutes = self.bot.config.get_min_schedule_interval()
        msg = f"""💮 - *HELP MENU* - 💮

*⚜️ Custom Content Commands:*
- /report `<url>` - Report dengan custom content & keyword
- /schedule `<url>` `<minutes>` - Schedule dengan custom content & keyword
━━━━━━━━━━━━━━━━━━━━━━
*⚜️ Quick Commands (Auto Content):*
- /quickreport `<url>` - Report cepat (auto content)
- /quickschedule `<url>` `<minutes>` - Schedule cepat (auto content)
━━━━━━━━━━━━━━━━━━━━━━
*⚜️ Schedule Management:*
- /schedules - View your active schedules
- /stop `<url>` - Stop a specific schedule
- /stopall - Stop all your schedules
━━━━━━━━━━━━━━━━━━━━━━
*⚜️ Other Commands:*
- /start - Start bot & view status
- /stats - View your statistics
- /cancel - Cancel current operation
- /help - Show this help menu"""
        if is_owner:
            msg += f"""
━━━━━━━━━━━━━━━━━━━━━━
*👑 Admin Commands:*
- /add `<id>` - Add permanent access
- /add `<id>` `<hours>` - Add temporary access
- /remove `<id>` - Remove user access
- /list - List all users
- /reset `<id>` - Reset user reports & cooldowns
- /proxystats - View proxy statistics
- /reloadproxy - Reload proxy list"""
        msg += "\n\n💡 *Questions?* Contact @AXVDIGITAL"
        await update.message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)

async def post_init(application: Application):
    bot = application.bot_data["bot_instance"]
    bot.schedule_manager = ScheduleManager(bot)
    bot.schedule_manager.start()
    print("✅ Schedule manager started (async mode)")
    bot.proxy_auto_updater = ProxyAutoUpdater(proxy_manager)
    bot.proxy_auto_updater.start()
    print("✅ Proxy auto-updater started")

def main():
    cleanup_orphaned_extensions()
    config = Config()
    if not config.is_configured():
        print(f"❌ Configuration incomplete! Edit: {os.path.abspath('config.json')}")
        return
    print(f"✅ Config loaded | Owner: {config.get_owner_id()} | Proxies: {proxy_manager.get_total_proxies()}")
    bot = EnhancedTelegramBot(config)
    handlers = EnhancedBotHandlers(bot)
    app = Application.builder().token(config.get_token()).build()
    app.bot_data["bot_instance"] = bot
    app.post_init = post_init
    report_conv_handler = ConversationHandler(
        entry_points=[CommandHandler("report", handlers.report_start)],
        states={
            # WAITING_CONTENT dihapus - content_type auto-filled
            WAITING_KEYWORD: [
                MessageHandler(filters.TEXT & ~filters.COMMAND, handlers.receive_keyword)
            ],
        },
        fallbacks=[CommandHandler("cancel", handlers.cancel_conversation)],
        per_user=True,
        per_chat=True,
    )
    schedule_conv_handler = ConversationHandler(
        entry_points=[CommandHandler("schedule", handlers.schedule_start)],
        states={
            # WAITING_SCHEDULE_CONTENT dihapus - content_type auto-filled
            WAITING_SCHEDULE_KEYWORD: [
                MessageHandler(filters.TEXT & ~filters.COMMAND, handlers.receive_schedule_keyword)
            ],
        },
        fallbacks=[CommandHandler("cancel", handlers.cancel_conversation)],
        per_user=True,
        per_chat=True,
    )
    app.add_handler(report_conv_handler)
    app.add_handler(schedule_conv_handler)
    app.add_handler(CommandHandler("start", handlers.start))
    app.add_handler(CommandHandler("quickreport", handlers.quick_report_url))
    app.add_handler(CommandHandler("quickschedule", handlers.quick_schedule_url))
    app.add_handler(CommandHandler("schedules", handlers.schedules_command))
    app.add_handler(CommandHandler("stop", handlers.stop_schedule_command))
    app.add_handler(CommandHandler("stopall", handlers.stopall_command))
    app.add_handler(CommandHandler("proxystats", handlers.proxystats_command))
    app.add_handler(CommandHandler("reloadproxy", handlers.reloadproxy_command))
    app.add_handler(CommandHandler("stats", handlers.stats))
    app.add_handler(CommandHandler("status", handlers.status_command))
    app.add_handler(CommandHandler("help", handlers.help_command))
    app.add_handler(CommandHandler("add", handlers.add_command))
    app.add_handler(CommandHandler("remove", handlers.remove_command))
    app.add_handler(CommandHandler("list", handlers.list_command))
    app.add_handler(CommandHandler("reset", handlers.reset_command))
    app.add_handler(CommandHandler("cancel", handlers.cancel_conversation))
    print("=" * 50)
    print("🚀 BOT STARTED")
    print("=" * 50)
    try:
        app.run_polling(allowed_updates=Update.ALL_TYPES)
    except KeyboardInterrupt:
        print("\n🛑 BOT STOPPED")
        if bot.schedule_manager:
            bot.schedule_manager.stop()
        if bot.proxy_auto_updater:
            bot.proxy_auto_updater.stop()
        bot.executor.shutdown(wait=True)
    except Exception as e:
        print(f"❌ Error: {e}")
        if bot.schedule_manager:
            bot.schedule_manager.stop()
        if bot.proxy_auto_updater:
            bot.proxy_auto_updater.stop()
        bot.executor.shutdown(wait=True)
if __name__ == "__main__":
    main()



### CHROMIUM