HEX
Server: LiteSpeed
System: Linux php-prod-1.spaceapp.ru 5.15.0-157-generic #167-Ubuntu SMP Wed Sep 17 21:35:53 UTC 2025 x86_64
User: xnsbb3110 (1041)
PHP: 8.1.33
Disabled: NONE
Upload Files
File: //proc/676643/root/usr/local/CyberCP/aiScanner/aiScannerManager.py
import requests
import json
import uuid
import secrets
from datetime import datetime, timedelta
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.utils import timezone
from django.conf import settings
from django.urls import reverse
from django.contrib import messages
from loginSystem.models import Administrator
from .models import AIScannerSettings, ScanHistory, FileAccessToken
from plogical.acl import ACLManager
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging


class AIScannerManager:
    AI_SCANNER_API_BASE = 'https://platform.cyberpersons.com/ai-scanner'
    
    def __init__(self):
        self.logger = logging
    
    def scannerHome(self, request, userID):
        """Main AI Scanner page"""
        try:
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    return ACLManager.loadError()
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                self.logger.writeToFile(f'[AIScannerManager.scannerHome] aiScannerAccess field not found, allowing access')
                pass
            
            # Get or create scanner settings
            scanner_settings, created = AIScannerSettings.objects.get_or_create(
                admin=admin,
                defaults={'balance': 0.0000, 'is_payment_configured': False}
            )
            
            # Get current pricing from API
            pricing_data = self.get_ai_scanner_pricing()
            
            # Get recent scan history with ACL respect
            if currentACL['admin'] == 1:
                # Admin can see all scans
                recent_scans = ScanHistory.objects.all().order_by('-started_at')[:10]
            else:
                # Users can only see their own scans and their sub-users' scans
                user_admins = ACLManager.loadUserObjects(userID)
                recent_scans = ScanHistory.objects.filter(admin__in=user_admins).order_by('-started_at')[:10]
            
            # Get current balance if payment is configured
            current_balance = scanner_settings.balance
            self.logger.writeToFile(f'[AIScannerManager.scannerHome] Stored balance: {current_balance}')
            
            if scanner_settings.is_payment_configured:
                # Try to fetch latest balance from API (now supports flexible auth)
                self.logger.writeToFile(f'[AIScannerManager.scannerHome] Fetching balance from API...')
                api_balance = self.get_account_balance(scanner_settings.api_key)
                self.logger.writeToFile(f'[AIScannerManager.scannerHome] API returned balance: {api_balance}')
                
                if api_balance is not None:
                    scanner_settings.balance = api_balance
                    scanner_settings.save()
                    current_balance = api_balance
                    self.logger.writeToFile(f'[AIScannerManager.scannerHome] Updated balance to: {current_balance}')
                else:
                    self.logger.writeToFile(f'[AIScannerManager.scannerHome] API balance call failed, keeping stored balance: {current_balance}')
            
            # Check VPS free scans availability
            server_ip = ACLManager.fetchIP()
            vps_info = self.check_vps_free_scans(server_ip)
            
            # Get user's websites for scan selection using ACL-aware method
            try:
                websites = ACLManager.findWebsiteObjects(currentACL, userID)
                self.logger.writeToFile(f'[AIScannerManager.scannerHome] Found {len(websites)} websites for {admin.userName}')
            except Exception as e:
                self.logger.writeToFile(f'[AIScannerManager.scannerHome] Error fetching websites: {str(e)}')
                websites = []
            
            # Build context safely
            self.logger.writeToFile(f'[AIScannerManager.scannerHome] Building context for {admin.userName}')
            
            context = {
                'admin': admin,
                'scanner_settings': scanner_settings,
                'pricing_data': pricing_data,
                'recent_scans': recent_scans,
                'current_balance': current_balance,
                'websites': websites,
                'is_payment_configured': scanner_settings.is_payment_configured,
                'vps_info': vps_info,
                'server_ip': server_ip,
            }
            
            self.logger.writeToFile(f'[AIScannerManager.scannerHome] Context built successfully, rendering template')
            
            return render(request, 'aiScanner/scanner.html', context)
            
        except Exception as e:
            import traceback
            self.logger.writeToFile(f'[AIScannerManager.scannerHome] Error: {str(e)}')
            self.logger.writeToFile(f'[AIScannerManager.scannerHome] Traceback: {traceback.format_exc()}')
            
            return render(request, 'aiScanner/scanner.html', {
                'error': f'Failed to load AI Scanner page: {str(e)}',
                'is_payment_configured': False,
                'websites': [],
                'recent_scans': [],
                'current_balance': 0,
                'pricing_data': None
            })
    
    def setupPayment(self, request, userID):
        """Setup payment method for AI scanner"""
        try:
            if request.method != 'POST':
                return JsonResponse({'success': False, 'error': 'Invalid request method'})
            
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    return JsonResponse({'success': False, 'error': 'Access denied'})
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                pass
            
            # Get admin email and domain
            cyberpanel_host = request.get_host()  # Keep full host including port
            cyberpanel_domain = cyberpanel_host.split(':')[0]  # Domain only for email fallback
            admin_email = admin.email if hasattr(admin, 'email') and admin.email else f'{admin.userName}@{cyberpanel_domain}'
            
            self.logger.writeToFile(f'[AIScannerManager.setupPayment] Admin: {admin.userName}, Email: {admin_email}, Host: {cyberpanel_host}')
            
            # Setup payment with AI Scanner API
            self.logger.writeToFile(f'[AIScannerManager.setupPayment] Attempting payment setup for {admin_email} on {cyberpanel_host}')
            setup_data = self.setup_ai_scanner_payment(admin_email, cyberpanel_host)
            
            if setup_data:
                self.logger.writeToFile(f'[AIScannerManager.setupPayment] Payment setup successful for {admin_email}')
                return JsonResponse({
                    'success': True,
                    'payment_url': setup_data['payment_url'],
                    'token': setup_data['token']
                })
            else:
                self.logger.writeToFile(f'[AIScannerManager.setupPayment] Payment setup failed for {admin_email}')
                return JsonResponse({
                    'success': False,
                    'error': 'Failed to setup payment. Please check the logs and try again.'
                })
                
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.setupPayment] Error: {str(e)}')
            return JsonResponse({'success': False, 'error': 'Internal server error'})
    
    def setupComplete(self, request, userID):
        """Handle return from payment setup"""
        try:
            admin = Administrator.objects.get(pk=userID)
            status = request.GET.get('status')
            
            # Log all URL parameters for debugging
            self.logger.writeToFile(f'[AIScannerManager.setupComplete] All URL params: {dict(request.GET)}')
            
            if status == 'success':
                api_key = request.GET.get('api_key')
                balance = request.GET.get('balance', '0.00')
                charged = request.GET.get('charged') == 'true'
                amount = request.GET.get('amount', '0.00')
                
                self.logger.writeToFile(f'[AIScannerManager.setupComplete] API Key: {api_key[:20] if api_key else "None"}...')
                self.logger.writeToFile(f'[AIScannerManager.setupComplete] Balance from URL: {balance}')
                self.logger.writeToFile(f'[AIScannerManager.setupComplete] Charged: {charged}, Amount: {amount}')
                
                if api_key:
                    try:
                        # Convert balance to float with error handling
                        balance_float = float(balance) if balance else 0.0
                        
                        # Update scanner settings
                        scanner_settings, created = AIScannerSettings.objects.get_or_create(
                            admin=admin,
                            defaults={
                                'api_key': api_key,
                                'balance': balance_float,
                                'is_payment_configured': True
                            }
                        )
                        
                        if not created:
                            # Update existing record
                            scanner_settings.api_key = api_key
                            scanner_settings.balance = balance_float
                            scanner_settings.is_payment_configured = True
                            scanner_settings.save()
                            self.logger.writeToFile(f'[AIScannerManager.setupComplete] Updated existing scanner settings')
                        else:
                            self.logger.writeToFile(f'[AIScannerManager.setupComplete] Created new scanner settings')
                        
                        # Verify the save worked
                        scanner_settings.refresh_from_db()
                        self.logger.writeToFile(f'[AIScannerManager.setupComplete] Final state - API Key: {scanner_settings.api_key[:20] if scanner_settings.api_key else "None"}..., Balance: {scanner_settings.balance}, Configured: {scanner_settings.is_payment_configured}')
                        
                        # Success message
                        if charged:
                            messages.success(request, f'Payment setup successful! ${amount} charged to your card. You have ${balance} credit.')
                        else:
                            messages.success(request, f'Payment setup successful! You have ${balance} credit.')
                        
                        self.logger.writeToFile(f'[AIScannerManager] Payment setup completed for {admin.userName} with balance ${balance}')
                        
                    except ValueError as e:
                        self.logger.writeToFile(f'[AIScannerManager.setupComplete] Balance conversion error: {str(e)}')
                        messages.error(request, 'Payment setup completed but balance format invalid.')
                    except Exception as e:
                        self.logger.writeToFile(f'[AIScannerManager.setupComplete] Database save error: {str(e)}')
                        messages.error(request, 'Payment setup completed but failed to save settings.')
                else:
                    self.logger.writeToFile(f'[AIScannerManager.setupComplete] No API key received in success callback')
                    messages.error(request, 'Payment setup completed but API key not received.')
                    
            elif status == 'partial_success':
                # Handle partial success (payment method added but charge failed)
                api_key = request.GET.get('api_key')
                if api_key:
                    try:
                        scanner_settings, created = AIScannerSettings.objects.get_or_create(
                            admin=admin,
                            defaults={
                                'api_key': api_key,
                                'balance': 0.0,
                                'is_payment_configured': True
                            }
                        )
                        
                        if not created:
                            scanner_settings.api_key = api_key
                            scanner_settings.is_payment_configured = True
                            scanner_settings.save()
                        
                        messages.warning(request, 'Payment method added but initial charge failed. Please add funds manually.')
                        self.logger.writeToFile(f'[AIScannerManager] Partial payment setup for {admin.userName}')
                    except Exception as e:
                        self.logger.writeToFile(f'[AIScannerManager.setupComplete] Partial success save error: {str(e)}')
                        messages.error(request, 'Payment method setup partially failed.')
                else:
                    messages.error(request, 'Payment method setup failed - no API key received.')
                    
            elif status in ['failed', 'cancelled', 'error']:
                error = request.GET.get('error', 'Payment setup failed')
                messages.error(request, f'Payment setup failed: {error}')
                self.logger.writeToFile(f'[AIScannerManager] Payment setup failed for {admin.userName}: {error}')
            
            return redirect('aiScannerHome')
            
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.setupComplete] Error: {str(e)}')
            messages.error(request, 'An error occurred during payment setup.')
            return redirect('aiScannerHome')
    
    def startScan(self, request, userID):
        """Start a new AI security scan"""
        try:
            if request.method != 'POST':
                return JsonResponse({'success': False, 'error': 'Invalid request method'})
            
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    return JsonResponse({'success': False, 'error': 'Access denied'})
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                pass
            
            # Check VPS free scans availability first
            server_ip = ACLManager.fetchIP()
            vps_info = self.check_vps_free_scans(server_ip)
            
            # If VPS is eligible for free scans, get or create API key
            vps_api_key = None
            vps_key_data = None
            if (vps_info.get('success') and 
                vps_info.get('is_vps') and 
                vps_info.get('free_scans_available', 0) > 0):
                
                self.logger.writeToFile(f'[AIScannerManager.startScan] VPS eligible for free scans, getting API key for IP: {server_ip}')
                vps_key_data = self.get_or_create_vps_api_key(server_ip)
                
                if vps_key_data:
                    vps_api_key = vps_key_data.get('api_key')
                    free_scans_remaining = vps_key_data.get('free_scans_remaining', 0)
                    self.logger.writeToFile(f'[AIScannerManager.startScan] VPS API key obtained, {free_scans_remaining} free scans remaining')
                else:
                    self.logger.writeToFile(f'[AIScannerManager.startScan] Failed to get VPS API key')
                    return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS for free scans'})
            
            # Get scanner settings (only required if not using VPS free scan)
            scanner_settings = None
            if not vps_api_key:
                try:
                    scanner_settings = AIScannerSettings.objects.get(admin=admin)
                    if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
                        return JsonResponse({'success': False, 'error': 'Payment not configured'})
                except AIScannerSettings.DoesNotExist:
                    return JsonResponse({'success': False, 'error': 'Scanner not configured'})
            
            # Parse request data
            data = json.loads(request.body)
            domain = data.get('domain')
            scan_type = data.get('scan_type', 'full')
            
            if not domain:
                return JsonResponse({'success': False, 'error': 'Domain is required'})
            
            # Validate domain belongs to user using ACL-aware method
            from websiteFunctions.models import Websites
            try:
                # Check if user has access to this domain through ACL system
                if not ACLManager.checkOwnership(domain, admin, currentACL):
                    return JsonResponse({'success': False, 'error': 'Access denied to this domain'})
                
                # Get the website object (we know it exists due to checkOwnership)
                website = Websites.objects.get(domain=domain)
            except Websites.DoesNotExist:
                return JsonResponse({'success': False, 'error': 'Domain not found'})
            
            # Generate scan ID and file access token
            scan_id = f'cp_{uuid.uuid4().hex[:12]}'
            file_access_token = self.generate_file_access_token()
            
            # Create scan history record
            scan_history = ScanHistory.objects.create(
                admin=admin,
                scan_id=scan_id,
                domain=domain,
                scan_type=scan_type,
                status='pending'
            )
            
            # Create file access token
            FileAccessToken.objects.create(
                token=file_access_token,
                scan_history=scan_history,
                domain=domain,
                wp_path=f'/home/{domain}/public_html',  # Adjust path as needed
                expires_at=timezone.now() + timedelta(hours=2)
            )
            
            # Submit scan to AI Scanner API
            callback_url = f"https://{request.get_host()}/api/ai-scanner/callback"
            file_access_base_url = f"https://{request.get_host()}/api/ai-scanner/"
            
            # Use VPS API key if available, otherwise use regular scanner settings
            api_key_to_use = vps_api_key if vps_api_key else scanner_settings.api_key
            
            scan_response = self.submit_wordpress_scan(
                api_key_to_use,
                domain,
                scan_type,
                callback_url,
                file_access_token,
                file_access_base_url,
                scan_id,
                server_ip
            )
            
            if scan_response:
                scan_history.status = 'running'
                scan_history.save()
                
                # Create appropriate success message
                if vps_api_key:
                    message = f'Free VPS scan started successfully! {vps_key_data.get("free_scans_remaining", 0)} free scans remaining.'
                else:
                    message = 'Scan started successfully'
                
                return JsonResponse({
                    'success': True,
                    'scan_id': scan_id,
                    'message': message
                })
            else:
                scan_history.status = 'failed'
                scan_history.error_message = 'Failed to submit scan to AI Scanner API'
                scan_history.save()
                
                return JsonResponse({'success': False, 'error': 'Failed to start scan'})
                
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.startScan] Error: {str(e)}')
            return JsonResponse({'success': False, 'error': 'Internal server error'})
    
    def refreshBalance(self, request, userID):
        """Refresh account balance from API"""
        try:
            if request.method != 'POST':
                return JsonResponse({'success': False, 'error': 'Invalid request method'})
            
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    return JsonResponse({'success': False, 'error': 'Access denied'})
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                pass
            
            # Get scanner settings
            try:
                scanner_settings = AIScannerSettings.objects.get(admin=admin)
                if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
                    return JsonResponse({'success': False, 'error': 'Payment not configured'})
            except AIScannerSettings.DoesNotExist:
                return JsonResponse({'success': False, 'error': 'Scanner not configured'})
            
            # Fetch balance from API
            api_balance = self.get_account_balance(scanner_settings.api_key)
            
            if api_balance is not None:
                old_balance = scanner_settings.balance
                scanner_settings.balance = api_balance
                scanner_settings.save()
                
                self.logger.writeToFile(f'[AIScannerManager.refreshBalance] Updated balance from ${old_balance} to ${api_balance} for {admin.userName}')
                
                return JsonResponse({
                    'success': True,
                    'balance': float(api_balance),
                    'message': f'Balance refreshed: ${api_balance:.4f}'
                })
            else:
                return JsonResponse({'success': False, 'error': 'Failed to fetch balance from API'})
                
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.refreshBalance] Error: {str(e)}')
            return JsonResponse({'success': False, 'error': 'Internal server error'})
    
    def addPaymentMethod(self, request, userID):
        """Add a new payment method for the user"""
        try:
            if request.method != 'POST':
                return JsonResponse({'success': False, 'error': 'Invalid request method'})
            
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    return JsonResponse({'success': False, 'error': 'Access denied'})
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                pass
            
            # Check if user has scanner configured (create if VPS user)
            try:
                scanner_settings = AIScannerSettings.objects.get(admin=admin)
                if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
                    # Check if this is a VPS with free scans
                    server_ip = ACLManager.fetchIP()
                    vps_info = self.check_vps_free_scans(server_ip)
                    
                    if vps_info.get('is_vps'):
                        # VPS users can add payment methods without initial setup
                        # Get or create VPS API key
                        vps_key_data = self.get_or_create_vps_api_key(server_ip)
                        if vps_key_data and vps_key_data.get('api_key'):
                            # Use VPS API key for adding payment method
                            api_key_to_use = vps_key_data.get('api_key')
                        else:
                            return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS'})
                    else:
                        return JsonResponse({'success': False, 'error': 'Initial payment setup required first'})
                else:
                    api_key_to_use = scanner_settings.api_key
            except AIScannerSettings.DoesNotExist:
                # Check if this is a VPS with free scans
                server_ip = ACLManager.fetchIP()
                vps_info = self.check_vps_free_scans(server_ip)
                
                if vps_info.get('is_vps'):
                    # VPS users can add payment methods without initial setup
                    # Get or create VPS API key
                    vps_key_data = self.get_or_create_vps_api_key(server_ip)
                    if vps_key_data and vps_key_data.get('api_key'):
                        # Use VPS API key for adding payment method
                        api_key_to_use = vps_key_data.get('api_key')
                    else:
                        return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS'})
                else:
                    return JsonResponse({'success': False, 'error': 'Scanner not configured'})
            
            # Get admin email and domain
            cyberpanel_host = request.get_host()  # Keep full host including port
            cyberpanel_domain = cyberpanel_host.split(':')[0]  # Domain only for email fallback
            admin_email = admin.email if hasattr(admin, 'email') and admin.email else f'{admin.userName}@{cyberpanel_domain}'
            
            self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Setting up new payment method for {admin.userName} (API key authentication)')
            
            # Call platform API to add payment method
            setup_data = self.setup_add_payment_method(api_key_to_use, admin_email, cyberpanel_host)
            
            if setup_data:
                self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Payment method setup successful for {admin_email}')
                return JsonResponse({
                    'success': True,
                    'setup_url': setup_data['setup_url'],
                    'token': setup_data.get('token', '')
                })
            else:
                self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Payment method setup failed for {admin_email}')
                return JsonResponse({
                    'success': False,
                    'error': 'Failed to setup payment method. Please try again.'
                })
                
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Error: {str(e)}')
            return JsonResponse({'success': False, 'error': 'Internal server error'})
    
    def paymentMethodComplete(self, request, userID):
        """Handle return from adding payment method"""
        try:
            admin = Administrator.objects.get(pk=userID)
            
            # Load ACL permissions
            currentACL = ACLManager.loadedACL(userID)
            
            # Check ACL permissions (with fallback for new field)
            try:
                if currentACL.get('aiScannerAccess', 1) == 0:
                    messages.error(request, 'Access denied to AI Scanner')
                    return redirect('dashboard')
            except (AttributeError, KeyError):
                # Field doesn't exist yet, allow access for now
                pass
            
            status = request.GET.get('status')
            
            # Log all URL parameters for debugging
            self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] All URL params: {dict(request.GET)}')
            
            if status == 'success':
                payment_method_id = request.GET.get('payment_method_id')
                card_last4 = request.GET.get('card_last4')
                card_brand = request.GET.get('card_brand')
                
                self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] Payment method added: {payment_method_id} ({card_brand} ****{card_last4})')
                
                if payment_method_id:
                    messages.success(request, f'Payment method added successfully! New {card_brand} card ending in {card_last4}.')
                    self.logger.writeToFile(f'[AIScannerManager] Payment method added for {admin.userName}: {card_brand} ****{card_last4}')
                else:
                    messages.success(request, 'Payment method added successfully!')
                    
            elif status in ['failed', 'cancelled', 'error']:
                error = request.GET.get('error', 'Failed to add payment method')
                messages.error(request, f'Failed to add payment method: {error}')
                self.logger.writeToFile(f'[AIScannerManager] Payment method add failed for {admin.userName}: {error}')
            
            return redirect('aiScannerHome')
            
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] Error: {str(e)}')
            messages.error(request, 'An error occurred while adding payment method.')
            return redirect('aiScannerHome')
    
    def scanCallback(self, request):
        """Handle scan results callback from AI Scanner API"""
        try:
            if request.method != 'POST':
                return JsonResponse({'success': False, 'error': 'Invalid request method'})
            
            data = json.loads(request.body)
            scan_id = data.get('scan_id')
            status = data.get('status')
            
            if not scan_id:
                return JsonResponse({'success': False, 'error': 'Scan ID required'})
            
            # Find scan history record
            try:
                scan_history = ScanHistory.objects.get(scan_id=scan_id)
            except ScanHistory.DoesNotExist:
                self.logger.writeToFile(f'[AIScannerManager.scanCallback] Scan not found: {scan_id}')
                return JsonResponse({'success': False, 'error': 'Scan not found'})
            
            # Update scan status and results
            scan_history.status = status
            scan_history.completed_at = timezone.now()
            
            if status == 'completed':
                findings = data.get('findings', [])
                summary = data.get('summary', {})
                cost_usd = data.get('cost_usd', 0)
                files_scanned = data.get('files_scanned', 0)
                
                scan_history.set_findings(findings)
                scan_history.set_summary(summary)
                scan_history.cost_usd = cost_usd
                scan_history.files_scanned = files_scanned
                scan_history.issues_found = len(findings)
                
                # Update user balance
                scanner_settings = scan_history.admin.ai_scanner_settings
                if cost_usd and scanner_settings.balance >= cost_usd:
                    scanner_settings.balance -= cost_usd
                    scanner_settings.save()
                
                self.logger.writeToFile(f'[AIScannerManager] Scan completed: {scan_id}, Cost: ${cost_usd}, Issues: {len(findings)}')
                
            elif status == 'failed':
                error_message = data.get('error', 'Scan failed')
                scan_history.error_message = error_message
                self.logger.writeToFile(f'[AIScannerManager] Scan failed: {scan_id}, Error: {error_message}')
            
            scan_history.save()
            
            # Deactivate file access tokens
            FileAccessToken.objects.filter(scan_history=scan_history).update(is_active=False)
            
            return JsonResponse({'success': True})
            
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.scanCallback] Error: {str(e)}')
            return JsonResponse({'success': False, 'error': 'Internal server error'})
    
    # API Helper Methods
    
    def get_ai_scanner_pricing(self):
        """Get current pricing from AI Scanner API"""
        try:
            response = requests.get(f'{self.AI_SCANNER_API_BASE}/api/plan/', timeout=10)
            if response.status_code == 200:
                return response.json()
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.get_ai_scanner_pricing] Error: {str(e)}')
            return None
    
    def setup_ai_scanner_payment(self, user_email, cyberpanel_host):
        """Setup payment method with AI Scanner API"""
        try:
            payload = {
                'email': user_email,
                'domain': cyberpanel_host.split(':')[0],  # Send domain without port
                'return_url': f'https://{cyberpanel_host}/aiscanner/setup-complete/'  # Include port in URL
            }
            
            self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Sending request to: {self.AI_SCANNER_API_BASE}/cyberpanel/setup-payment/')
            self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Payload: {payload}')
            
            response = requests.post(
                f'{self.AI_SCANNER_API_BASE}/cyberpanel/setup-payment/',
                json=payload,
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Response status: {response.status_code}')
            self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Response content: {response.text}')
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    return {
                        'payment_url': data['payment_url'],
                        'token': data['token']
                    }
                else:
                    self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] API returned success=false: {data.get("error", "Unknown error")}')
            else:
                self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Non-200 status code: {response.status_code}')
            
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Exception: {str(e)}')
            return None
    
    def get_account_balance(self, api_key):
        """Get current account balance"""
        try:
            self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Requesting balance from: {self.AI_SCANNER_API_BASE}/api/account/balance/')
            
            response = requests.get(
                f'{self.AI_SCANNER_API_BASE}/api/account/balance/',
                headers={'X-API-Key': api_key},
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Response status: {response.status_code}')
            self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Response content: {response.text}')
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    # Use the new balance_usd field from flexible API
                    balance = float(data.get('balance_usd', data.get('balance', 0)))
                    auth_method = data.get('authenticated_via', 'unknown')
                    self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Parsed balance: {balance} (auth: {auth_method})')
                    return balance
                else:
                    # Even failed responses now include balance_usd hint
                    balance_hint = data.get('balance_usd', 0)
                    self.logger.writeToFile(f'[AIScannerManager.get_account_balance] API returned success=false: {data.get("error", "Unknown error")} (balance hint: {balance_hint})')
                    # Return the balance hint if available, even on auth failure
                    if balance_hint > 0:
                        return float(balance_hint)
            else:
                self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Non-200 status code: {response.status_code}')
            
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Exception: {str(e)}')
            return None
    
    def submit_wordpress_scan(self, api_key, domain, scan_type, callback_url, file_access_token, file_access_base_url, scan_id, server_ip):
        """Submit scan request to AI Scanner API"""
        try:
            payload = {
                'domain': domain,
                'site_url': domain,
                'scan_type': scan_type,
                'cyberpanel_callback': callback_url,
                'file_access_token': file_access_token,
                'file_access_base_url': file_access_base_url,
                'scan_id': scan_id,
                'server_ip': server_ip
            }
            
            self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Submitting scan {scan_id} for {domain}')
            self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Payload: {payload}')
            
            response = requests.post(
                f'{self.AI_SCANNER_API_BASE}/api/scan/submit-v2/',
                headers={'X-API-Key': api_key},
                json=payload,
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Response status: {response.status_code}')
            self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Response content: {response.text}')
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    platform_scan_id = data.get('scan_id')
                    self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Platform assigned scan ID: {platform_scan_id}')
                    return platform_scan_id
                else:
                    self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Platform returned success=false: {data.get("error", "Unknown error")}')
            else:
                self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Non-200 status code: {response.status_code}')
            
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Error: {str(e)}')
            return None
    
    def get_scan_status(self, api_key, scan_id):
        """Get scan status from AI Scanner API"""
        try:
            response = requests.get(
                f'{self.AI_SCANNER_API_BASE}/api/scan/{scan_id}/status/',
                headers={'X-API-Key': api_key},
                timeout=10
            )
            
            if response.status_code == 200:
                return response.json()
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.get_scan_status] Error: {str(e)}')
            return None
    
    def get_scan_results(self, api_key, scan_id):
        """Get scan results from AI Scanner API"""
        try:
            response = requests.get(
                f'{self.AI_SCANNER_API_BASE}/api/scan/{scan_id}/results/',
                headers={'X-API-Key': api_key},
                timeout=10
            )
            
            if response.status_code == 200:
                return response.json()
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.get_scan_results] Error: {str(e)}')
            return None
    
    def check_vps_free_scans(self, server_ip):
        """Check if server IP belongs to VPS hosting and has free scans available"""
        try:
            self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Checking VPS free scans for IP: {server_ip}')
            
            response = requests.post(
                'https://platform.cyberpersons.com/ai-scanner/api/vps/check-free-scans/',
                json={'ip': server_ip},
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Response status: {response.status_code}')
            
            if response.status_code == 200:
                data = response.json()
                self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Response data: {data}')
                return data
            else:
                self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] API error: {response.text}')
                return {'success': False, 'is_vps': False, 'error': 'API call failed'}
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Error: {str(e)}')
            return {'success': False, 'is_vps': False, 'error': str(e)}

    def setup_add_payment_method(self, api_key, user_email, cyberpanel_host):
        """Setup additional payment method with AI Scanner API"""
        try:
            payload = {
                'domain': cyberpanel_host.split(':')[0],  # Send domain without port
                'return_url': f'https://{cyberpanel_host}/aiscanner/payment-method-complete/',  # Include port in URL
                'action': 'add_payment_method'  # Indicate this is adding a payment method, not initial setup
            }
            
            self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Sending request to: {self.AI_SCANNER_API_BASE}/cyberpanel/add-payment-method/')
            self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Payload: {payload}')
            
            response = requests.post(
                f'{self.AI_SCANNER_API_BASE}/cyberpanel/add-payment-method/',
                headers={'X-API-Key': api_key},
                json=payload,
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Response status: {response.status_code}')
            self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Response content: {response.text}')
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    return {
                        'setup_url': data['setup_url'],
                        'token': data.get('token', '')
                    }
                else:
                    self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] API returned success=false: {data.get("error", "Unknown error")}')
            else:
                self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Non-200 status code: {response.status_code}')
            
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Exception: {str(e)}')
            return None

    def get_or_create_vps_api_key(self, server_ip):
        """Get API key for VPS free scans from platform"""
        try:
            payload = {'server_ip': server_ip}
            
            self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Requesting VPS API key for IP: {server_ip}')
            
            response = requests.post(
                f'{self.AI_SCANNER_API_BASE}/api/vps/generate-api-key/',
                json=payload,
                headers={'Content-Type': 'application/json'},
                timeout=10
            )
            
            self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Response status: {response.status_code}')
            self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Response content: {response.text}')
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    return {
                        'api_key': data.get('api_key'),
                        'free_scans_remaining': data.get('free_scans_remaining'),
                        'account_type': data.get('account_type'),
                        'vps_name': data.get('vps_name'),
                        'vps_id': data.get('vps_id')
                    }
                else:
                    self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] API returned success=false: {data.get("error", "Unknown error")}')
            else:
                self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Non-200 status code: {response.status_code}')
            
            return None
        except Exception as e:
            self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Exception: {str(e)}')
            return None

    def generate_file_access_token(self):
        """Generate secure file access token"""
        return f'cp_{secrets.token_urlsafe(32)}'