import http.server
import socketserver
import subprocess
import os
import signal
import urllib.parse
import re
import urllib.request
import zipfile
import stat
import time
import threading
import socket

PORT = 8001
BASE_DIR = os.path.dirname(__file__)
HTML_TEMPLATE_PATH = os.path.join(BASE_DIR, 'taskmgr.html')
AUTO_CONFIG_TEMPLATE_PATH = os.path.join(BASE_DIR, 'auto-config.html')

# New template paths
ABOUT_TEMPLATE_PATH = os.path.join(BASE_DIR, 'about.html')
ANDROID_TEMPLATE_PATH = os.path.join(BASE_DIR, 'android.html')

CLI_APPS_DIR = os.path.expanduser('~/usr/local/bin')  # CLI apps directory
WEB_APPS_DIR = os.path.expanduser('~/apps')           # Web apps directory
AVAILABLE_APPS_URL = 'http://berrystore.sw7ft.com/bins/'
WEB_APPS_URL = 'http://berrystore.sw7ft.com/apps/'
# New: APKs directory
APKS_URL = 'http://berrystore.sw7ft.com/apks/'

PROFILE_FILE = os.path.expanduser('~/.profile')
app_ports = {}  # Store mapping of PID to port

# Performance optimization: Caching
CACHE_DURATION = 300  # 5 minutes cache
REQUEST_TIMEOUT = 10  # 10 seconds timeout
cache = {}

def get_cached_or_fetch(url, cache_key):
    """Get data from cache or fetch from URL with timeout"""
    current_time = time.time()
    
    # Check if we have cached data that's still valid
    if cache_key in cache:
        cached_time, cached_data = cache[cache_key]
        if current_time - cached_time < CACHE_DURATION:
            return cached_data
    
    # Fetch new data with timeout
    try:
        request = urllib.request.Request(url)
        with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT) as response:
            if response.status != 200:
                print(f"Failed to fetch {url}: Status {response.status}")
                return None
            data = response.read().decode()
            # Cache the result
            cache[cache_key] = (current_time, data)
            return data
    except (urllib.error.URLError, socket.timeout) as e:
        print(f"Error fetching {url}: {e}")
        # Return stale cache if available
        if cache_key in cache:
            cached_time, cached_data = cache[cache_key]
            print(f"Using stale cache for {cache_key}")
            return cached_data
        return None

class TaskManagerHandler(http.server.BaseHTTPRequestHandler):

    def do_GET(self):
        parsed_path = urllib.parse.urlparse(self.path)

        # Handle /action?...
        if self.path.startswith('/action'):
            params = urllib.parse.parse_qs(parsed_path.query)
            action = params.get('action', [''])[0]
            app_name = params.get('app_name', [''])[0]
            pid = params.get('pid', [''])[0]
            app_type = params.get('app_type', ['cli'])[0]

            if action == 'start' and app_name:
                self.start_app(app_name, app_type)
            elif action == 'stop' and pid:
                self.stop_app(pid)
            elif action == 'install' and app_name:
                self.install_app(app_name, app_type)
            elif action == 'delete' and app_name:
                self.delete_app(app_name, app_type)
            elif action == 'enable_auto_start' and app_name:
                self.enable_auto_start_app(app_name, app_type)
            elif action == 'disable_auto_start' and app_name:
                self.disable_auto_start_app(app_name)

            referer = self.headers.get('Referer', '/')
            self.send_response(303)
            self.send_header('Location', referer)
            self.end_headers()

        # Handle /auto-config page
        elif self.path.startswith('/auto-config'):
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            html = self.generate_auto_config_html()
            self.wfile.write(html.encode())

        # New: handle /about page
        elif self.path == '/about':
            self.serve_about_page()

        # New: handle /android page
        elif self.path == '/android':
            self.serve_android_page()

        # Handle lazy loading endpoints
        elif self.path == '/api/available-cli':
            self.serve_available_cli_json()
        elif self.path == '/api/available-web':
            self.serve_available_web_json()

        # Otherwise serve the main page
        else:
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            html = self.generate_html()
            self.wfile.write(html.encode())

    def serve_available_cli_json(self):
        """Serve available CLI apps as JSON for lazy loading"""
        try:
            html = self.generate_available_cli_html()
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(html.encode())
        except Exception as e:
            print(f"Error serving available CLI apps: {e}")
            self.send_error(500, 'Error loading CLI apps')

    def serve_available_web_json(self):
        """Serve available web apps as JSON for lazy loading"""
        try:
            html = self.generate_available_web_apps_html()
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(html.encode())
        except Exception as e:
            print(f"Error serving available web apps: {e}")
            self.send_error(500, 'Error loading web apps')

    # -------------------------------------------------------------------------
    # New methods for /about and /android
    # -------------------------------------------------------------------------
    def serve_about_page(self):
        """Serve the about.html page."""
        try:
            with open(ABOUT_TEMPLATE_PATH, 'r') as file:
                html = file.read()
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(html.encode())
        except Exception as e:
            print(f"Error loading about.html: {e}")
            self.send_error(500, 'Error loading about.html')

    def serve_android_page(self):
        """Serve the android.html page, after populating it with the list of .apk files."""
        try:
            # Fetch the list of .apk files
            apks = self.fetch_android_apks()

            with open(ANDROID_TEMPLATE_PATH, 'r') as file:
                html = file.read()

            # Build a simple list or table of the APK files
            # For each APK, we can provide a download link or just list them
            if not apks:
                apks_html = '<p>No APKs found.</p>'
            else:
                apks_html = '<ul>'
                for apk in apks:
                    apk_link = urllib.parse.urljoin(APKS_URL, apk)
                    apks_html += f'<li><a href="{apk_link}" target="_blank">{apk}</a></li>'
                apks_html += '</ul>'

            # Replace a placeholder in android.html, e.g. <!-- apks_list -->, with our content
            html = html.replace('<!-- apks_list -->', apks_html)

            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(html.encode())
        except Exception as e:
            print(f"Error serving android.html: {e}")
            self.send_error(500, 'Error serving android.html')

    def install_app(self, app_name, app_type='cli'):
        try:
            if app_type == 'cli':
                download_url = urllib.parse.urljoin(AVAILABLE_APPS_URL, app_name)
                install_dir = CLI_APPS_DIR
            elif app_type == 'web':
                download_url = urllib.parse.urljoin(WEB_APPS_URL, app_name)
                install_dir = WEB_APPS_DIR
            else:
                print(f"Unknown app type:{app_type}" )
                return

            print(f"Downloading {download_url}")
            request = urllib.request.Request(download_url)
            with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT) as response:
                if response.status != 200:
                    print(f"Failed to download {download_url}: Status {response.status}")
                    return
                zip_data = response.read()

            if not os.path.exists(install_dir):
                os.makedirs(install_dir)
                print(f"Created installation directory at: {install_dir}")

            zip_path = os.path.join(install_dir, app_name)
            with open(zip_path, 'wb') as f:
                f.write(zip_data)
            print(f"Downloaded {app_name} to {zip_path}")

            with zipfile.ZipFile(zip_path, 'r') as zip_ref:
                if app_type == 'web':
                    app_folder = os.path.splitext(app_name)[0]
                    extract_path = os.path.join(install_dir, app_folder)
                    os.makedirs(extract_path, exist_ok=True)
                    zip_ref.extractall(extract_path)
                    print(f"Extracted {app_name} to {extract_path}")
                else:
                    # For CLI apps, handle lib/ directories specially
                    lib_dir = os.path.expanduser('~/usr/local/lib')
                    bin_dir = os.path.expanduser('~/usr/local/bin')
                    
                    for member in zip_ref.namelist():
                        if member.startswith('lib/'):
                            # Extract lib files to ~/usr/local/lib/
                            # Remove 'lib/' prefix and extract to lib directory
                            lib_file = member[4:]  # Remove 'lib/' prefix
                            if lib_file:  # Skip empty directory entries
                                target_path = os.path.join(lib_dir, lib_file)
                                os.makedirs(os.path.dirname(target_path), exist_ok=True)
                                with zip_ref.open(member) as source, open(target_path, 'wb') as target:
                                    target.write(source.read())
                                print(f"Extracted library: {lib_file} to {lib_dir}")
                        elif member.startswith('bin/'):
                            # Extract bin files to ~/usr/local/bin/
                            bin_file = member[4:]  # Remove 'bin/' prefix
                            if bin_file:  # Skip empty directory entries
                                target_path = os.path.join(bin_dir, bin_file)
                                os.makedirs(os.path.dirname(target_path), exist_ok=True)
                                with zip_ref.open(member) as source, open(target_path, 'wb') as target:
                                    target.write(source.read())
                                print(f"Extracted binary: {bin_file} to {bin_dir}")
                        else:
                            # For other files, extract to bin directory
                            if not member.endswith('/'):  # Skip directories
                                target_path = os.path.join(bin_dir, member)
                                os.makedirs(os.path.dirname(target_path), exist_ok=True)
                                with zip_ref.open(member) as source, open(target_path, 'wb') as target:
                                    target.write(source.read())
                                print(f"Extracted file: {member} to {bin_dir}")

            # Make binaries executable
            if app_type == 'cli':
                self.ensure_cli_paths()
                extracted_files = zip_ref.namelist()
                for file in extracted_files:
                    if not file.endswith('/'):
                        if file.startswith('bin/'):
                            file_path = os.path.join(bin_dir, file[4:])
                        else:
                            file_path = os.path.join(bin_dir, file)
                        if os.path.exists(file_path):
                            os.chmod(file_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
                            print(f"Made {file_path} executable")

            os.remove(zip_path)
            print(f"Removed temporary zip file: {zip_path}")

        except Exception as e:
            print(f"Error installing app {app_name}: {e}")

    def ensure_cli_paths(self):
        """Ensure CLI paths exist"""
        for path in [CLI_APPS_DIR, os.path.expanduser('~/usr/local/lib')]:
            if not os.path.exists(path):
                os.makedirs(path)
                print(f"Created directory: {path}")

    def delete_app(self, app_name, app_type='cli'):
        try:
            if app_type == 'cli':
                app_path = os.path.join(CLI_APPS_DIR, app_name)
                if os.path.exists(app_path):
                    os.remove(app_path)
                    print(f"Deleted CLI app: {app_path}")
            elif app_type == 'web':
                app_path = os.path.join(WEB_APPS_DIR, app_name)
                if os.path.exists(app_path):
                    import shutil
                    shutil.rmtree(app_path)
                    print(f"Deleted web app: {app_path}")
        except Exception as e:
            print(f"Error deleting app {app_name}: {e}")

    def start_app(self, app_name, app_type='cli'):
        try:
            if app_type == 'web':
                app_path = os.path.join(WEB_APPS_DIR, app_name, 'app.py')
                if os.path.exists(app_path):
                    process = subprocess.Popen(['python3', app_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    print(f"Started web app: {app_name} with PID: {process.pid}")
                    
                    # Try to determine the port (this is a simplified approach)
                    # In a real implementation, you might want to parse the app.py file or use a different method
                    # For now, we'll use a simple port assignment strategy
                    port = 8000 + (process.pid % 1000)
                    app_ports[process.pid] = port
                else:
                    print(f"Web app not found: {app_path}")
            else:
                print(f"CLI app start not implemented for: {app_name}")
        except Exception as e:
            print(f"Error starting app {app_name}: {e}")

    def stop_app(self, pid):
        try:
            os.kill(int(pid), signal.SIGTERM)
            print(f"Stopped app with PID: {pid}")
            # Remove from port mapping
            if int(pid) in app_ports:
                del app_ports[int(pid)]
        except Exception as e:
            print(f"Error stopping app {pid}: {e}")

    def generate_html(self):
        try:
            with open(HTML_TEMPLATE_PATH, 'r') as file:
                html = file.read()

            processes_html = self.generate_processes_html()
            installed_apps_html = self.generate_installed_apps_html()
            
            # For performance, load available apps with lazy loading placeholders
            available_cli_html = '<div id="available-cli-loading">Loading CLI apps...</div>'
            available_web_html = '<div id="available-web-loading">Loading web apps...</div>'

            processes_html = processes_html if processes_html is not None else ''
            installed_apps_html = installed_apps_html if installed_apps_html is not None else ''

            html = html.replace('<!-- running_processes -->', processes_html)
            html = html.replace('<!-- installed_apps -->', installed_apps_html)
            html = html.replace('<!-- available_cli_apps -->', available_cli_html)
            html = html.replace('<!-- available_web_apps -->', available_web_html)

            # Add lazy loading JavaScript (ES5 compatible)
            lazy_script = '''
            <script>
            // ES5 compatible lazy loading
            function loadAvailableApps() {
                // Load CLI apps
                var cliContainer = document.getElementById('available-cli-loading');
                if (cliContainer) {
                    var xhr1 = new XMLHttpRequest();
                    xhr1.open('GET', '/api/available-cli', true);
                    xhr1.onreadystatechange = function() {
                        if (xhr1.readyState === 4) {
                            if (xhr1.status === 200) {
                                cliContainer.innerHTML = xhr1.responseText;
                            } else {
                                cliContainer.innerHTML = '<p>Error loading CLI apps</p>';
                            }
                        }
                    };
                    xhr1.send();
                }

                // Load web apps
                var webContainer = document.getElementById('available-web-loading');
                if (webContainer) {
                    var xhr2 = new XMLHttpRequest();
                    xhr2.open('GET', '/api/available-web', true);
                    xhr2.onreadystatechange = function() {
                        if (xhr2.readyState === 4) {
                            if (xhr2.status === 200) {
                                webContainer.innerHTML = xhr2.responseText;
                            } else {
                                webContainer.innerHTML = '<p>Error loading web apps</p>';
                            }
                        }
                    };
                    xhr2.send();
                }
            }

            // Load available apps after page loads
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', loadAvailableApps);
            } else {
                loadAvailableApps();
            }
            </script>
            '''
            html = html.replace('</body>', lazy_script + '</body>')

            return html
        except Exception as e:
            print(f'Error loading HTML template: {e}')
            return '<html><body><h1>Error loading HTML template</h1></body></html>'

    def generate_processes_html(self):
        try:
            processes = self.get_python_processes()
            rows = ''
            for pid, cmd in processes:
                launch_link = ''
                if int(pid) in app_ports:
                    port = app_ports[int(pid)]
                    launch_link = f' - <a href="http://0.0.0.0:{port}" target="_blank">Launch UI</a>'
                rows += f'<tr><td>{pid}</td><td class="scrollable-command">{cmd}</td><td><a href="/action?action=stop&pid={pid}">Stop</a>{launch_link}</td></tr>'
            return rows
        except Exception as e:
            print(f'Error generating processes HTML: {e}')
            return ''

    def generate_installed_apps_html(self):
        try:
            cli_apps = self.scan_apps_directory(CLI_APPS_DIR, 'cli')
            web_apps = self.scan_apps_directory(WEB_APPS_DIR, 'web')
            all_apps = cli_apps + web_apps

            if not all_apps:
                return '<p>No installed apps found.</p>'

            apps_html = '<table>'
            apps_html += '<tr><th>App Name</th><th>Type</th><th>Action</th></tr>'
            for app, app_type in all_apps:
                app_encoded = urllib.parse.quote(app)
                if app_type == 'web':
                    apps_html += f'<tr><td>{app}</td><td>Web App</td><td><a href="/action?action=start&app_name={app_encoded}&app_type=web">Start</a> | <a href="/action?action=delete&app_name={app_encoded}&app_type=web">Delete</a></td></tr>'
                else:
                    apps_html += f'<tr><td>{app}</td><td>CLI</td><td><a href="/action?action=delete&app_name={app_encoded}&app_type=cli">Delete</a></td></tr>'
            apps_html += '</table>'
            return apps_html
        except Exception as e:
            print(f'Error generating installed apps HTML: {e}')
            return ''

    def enable_auto_start_app(self, app_name, app_type='cli'):
        try:
            if app_type == 'web':
                app_command = f'python3 "{os.path.join(WEB_APPS_DIR, app_name, "app.py")}" &\n'
            else:
                app_command = f'python3 "{os.path.join(CLI_APPS_DIR, app_name)}" &\n'

            with open(PROFILE_FILE, 'r') as f:
                profile_contents = f.readlines()

            AUTO_START_MARKER_START = f'# <<< Auto-Start for {app_name} >>>'
            AUTO_START_MARKER_END = f'# <<< End Auto-Start for {app_name} >>>'

            if any(app_command.strip() in line.strip() for line in profile_contents):
                print(f"Auto-start for {app_name} is already enabled.")
                return

            with open(PROFILE_FILE, 'a') as f:
                f.write(f'\n{AUTO_START_MARKER_START}\n')
                f.write(app_command)
                f.write(f'{AUTO_START_MARKER_END}\n')

            print(f"Enabled auto-start for {app_name}.")
        except Exception as e:
            print(f"Error enabling auto start for {app_name}: {e}")

    def disable_auto_start_app(self, app_name):
        try:
            if not os.path.exists(PROFILE_FILE):
                print(f"{PROFILE_FILE} does not exist.")
                return

            AUTO_START_MARKER_START = f'# <<< Auto-Start for {app_name} >>>'
            AUTO_START_MARKER_END = f'# <<< End Auto-Start for {app_name} >>>'

            with open(PROFILE_FILE, 'r') as f:
                profile_lines = f.readlines()

            updated_lines = []
            skip = False
            for line in profile_lines:
                if line.strip() == AUTO_START_MARKER_START:
                    skip = True
                    continue
                if line.strip() == AUTO_START_MARKER_END:
                    skip = False
                    continue
                if not skip:
                    updated_lines.append(line)

            with open(PROFILE_FILE, 'w') as f:
                f.writelines(updated_lines)

            print(f"Disabled auto-start for {app_name}.")
        except Exception as e:
            print(f"Error disabling auto start for {app_name}: {e}")

    def generate_available_cli_html(self):
        try:
            available_zips = self.fetch_available_zips()
            if available_zips is None:
                return '<p>Error loading Command Line Utilities.</p>'
                
            installed_cli_apps = set(app for app, _ in self.scan_apps_directory(CLI_APPS_DIR, 'cli'))
            # Exclude installed apps
            available_zips = [z for z in available_zips if os.path.splitext(z)[0] not in installed_cli_apps]

            if not available_zips:
                return '<p>No Command Line Utilities available.</p>'

            apps_html = '<div class="apps-container">'
            for zip_file in available_zips:
                app_name = os.path.splitext(zip_file)[0]
                app_encoded = urllib.parse.quote(zip_file)
                apps_html += f'''
                <div class="app-item">
                    <div class="app-icon"></div>
                    <div class="app-name">{app_name}</div>
                    <a href="/action?action=install&app_name={app_encoded}&app_type=cli" class="install-button">Install</a>
                </div>
                '''
            apps_html += '</div>'
            return apps_html
        except Exception as e:
            print(f"Error generating available CLI apps HTML: {e}")
            return '<p>Error loading Command Line Utilities.</p>'

    def generate_available_web_apps_html(self):
        try:
            available_zips = self.fetch_web_apps()
            if available_zips is None:
                return '<p>Error loading Web Apps.</p>'
                
            installed_web_apps = set(app for app, _ in self.scan_apps_directory(WEB_APPS_DIR, 'web'))
            # Exclude installed apps
            available_zips = [z for z in available_zips if os.path.splitext(z)[0] not in installed_web_apps]

            if not available_zips:
                return '<p>No Web Apps available.</p>'

            apps_html = '<div class="apps-container">'
            for zip_file in available_zips:
                app_name = os.path.splitext(zip_file)[0]
                app_encoded = urllib.parse.quote(zip_file)
                apps_html += f'''
                <div class="app-item">
                    <div class="app-icon"></div>
                    <div class="app-name">{app_name}</div>
                    <a href="/action?action=install&app_name={app_encoded}&app_type=web" class="install-button">Install</a>
                </div>
                '''
            apps_html += '</div>'
            return apps_html
        except Exception as e:
            print(f"Error generating available Web Apps HTML: {e}")
            return '<p>Error loading Web Apps.</p>'

    # New: Fetch .apk files from the berrystore directory
    def fetch_android_apks(self):
        """
        Fetch and parse the listing of .apk files from APKS_URL.
        Returns a list of filenames (e.g. ['myapp.apk', 'anotherapp.apk', ...]).
        """
        html_content = get_cached_or_fetch(APKS_URL, 'android_apks')
        if html_content is None:
            return []

        try:
            # Look for all anchors linking to something ending in .apk
            apk_files = re.findall(r'href=["\']([^"\']+\.apk)["\']', html_content)
            # Just keep the base filenames
            apk_files = [os.path.basename(a) for a in apk_files]
            return apk_files
        except Exception as e:
            print(f"Error parsing Android APKs: {e}")
            return []

    def fetch_available_zips(self):
        html_content = get_cached_or_fetch(AVAILABLE_APPS_URL, 'available_cli_zips')
        if html_content is None:
            return None

        try:
            zip_files = re.findall(r'href=["\']([^"\']+\.zip)["\']', html_content)
            zip_files = [os.path.basename(z) for z in zip_files]
            return zip_files
        except Exception as e:
            print(f"Error parsing available CLI zips: {e}")
            return None

    def fetch_web_apps(self):
        html_content = get_cached_or_fetch(WEB_APPS_URL, 'available_web_apps')
        if html_content is None:
            return None

        try:
            zip_files = re.findall(r'href=["\']([^"\']+\.zip)["\']', html_content)
            zip_files = [os.path.basename(z) for z in zip_files]
            return zip_files
        except Exception as e:
            print(f"Error parsing web apps: {e}")
            return None

    def get_python_processes(self):
        """
        OPTIMIZED: Use more targeted pidin commands for better performance on QNX
        """
        processes = []
        try:
            # Option 1: Use pidin to filter by python processes more efficiently
            # This is much faster than 'pidin ar' as it targets specific processes
            output = subprocess.check_output(['pidin', '-p', 'python'], stderr=subprocess.DEVNULL).decode()
            lines = output.strip().split('\n')
            
            for line in lines:
                if 'python' in line and line.strip():
                    parts = line.split()
                    if len(parts) >= 2:
                        pid = parts[0]
                        cmd = ' '.join(parts[1:])
                        # Clean up command path for display
                        if '/data/' in cmd:
                            index = cmd.find('/data/') + len('/data/')
                            cmd = cmd[index:]
                        processes.append((pid, cmd))
        except subprocess.CalledProcessError:
            # Fallback: try alternative pidin approach
            try:
                # Option 2: Use pidin with format specifiers for specific fields only
                output = subprocess.check_output(['pidin', '-f', '%a %A %n %p'], stderr=subprocess.DEVNULL).decode()
                lines = output.strip().split('\n')
                
                for line in lines:
                    if 'python' in line and line.strip():
                        parts = line.split()
                        if len(parts) >= 2:
                            pid = parts[0]
                            cmd = ' '.join(parts[1:])
                            if '/data/' in cmd:
                                index = cmd.find('/data/') + len('/data/')
                                cmd = cmd[index:]
                            processes.append((pid, cmd))
            except subprocess.CalledProcessError:
                # Final fallback: use the original method but with timeout
                try:
                    output = subprocess.check_output(['pidin', 'ar'], timeout=5, stderr=subprocess.DEVNULL).decode()
                    lines = output.strip().split('\n')
                    for line in lines:
                        if 'python' in line:
                            parts = line.split()
                            if len(parts) >= 2:
                                pid = parts[0]
                                cmd = ' '.join(parts[1:])
                                if '/data/' in cmd:
                                    index = cmd.find('/data/') + len('/data/')
                                    cmd = cmd[index:]
                                processes.append((pid, cmd))
                except Exception as e:
                    print(f'Error getting processes (fallback): {e}')
        except Exception as e:
            print(f'Error getting processes: {e}')
        
        return processes

    def scan_apps_directory(self, directory, app_type):
        apps = []
        try:
            if not os.path.exists(directory):
                return apps
            for root, dirs, files in os.walk(directory):
                for file in files:
                    if app_type == 'web':
                        if file == 'app.py':
                            app_folder = os.path.relpath(root, directory)
                            apps.append((app_folder, 'web'))
                    else:
                        # CLI apps assumed to be executables without .py extension
                        if not file.endswith('.py'):
                            rel_path = os.path.relpath(os.path.join(root, file), directory)
                            apps.append((rel_path, 'cli'))
        except Exception as e:
            print(f'Error scanning apps directory: {e}')
        return apps

    def generate_auto_config_html(self):
        """
        Generate the HTML for the auto-config page, showing web apps
        with options to enable or disable auto start.
        """
        try:
            with open(AUTO_CONFIG_TEMPLATE_PATH, 'r') as file:
                html = file.read()

            all_apps = self.get_all_installed_apps()

            if not all_apps:
                apps_html = '<p>No installed web apps found.</p>'
            else:
                apps_html = '<table>'
                apps_html += '<tr><th>App Name</th><th>Type</th><th>Auto Start</th></tr>'
                for app, app_type in all_apps:
                    if app_type == 'web':  # Only include web apps
                        app_encoded = urllib.parse.quote(app)
                        if self.is_auto_start_enabled(app):
                            # Show Disable Auto Start link if enabled
                            apps_html += f'<tr><td>{app}</td><td>{app_type.upper()}</td><td><a href="/action?action=disable_auto_start&app_name={app_encoded}&app_type={app_type}">Disable Auto Start</a></td></tr>'
                        else:
                            # Show Enable Auto Start link if not enabled
                            apps_html += f'<tr><td>{app}</td><td>{app_type.upper()}</td><td><a href="/action?action=enable_auto_start&app_name={app_encoded}&app_type={app_type}">Enable Auto Start</a></td></tr>'
                apps_html += '</table>'

            html = html.replace('<!-- installed_apps_auto -->', apps_html)
            return html
        except Exception as e:
            print(f'Error loading auto-config HTML template: {e}')
            return '<html><body><h1>Error loading auto-config template</h1></body></html>'

    def get_all_installed_apps(self):
        cli_apps = self.scan_apps_directory(CLI_APPS_DIR, 'cli')
        web_apps = self.scan_apps_directory(WEB_APPS_DIR, 'web')
        return cli_apps + web_apps

    def is_auto_start_enabled(self, app_name):
        """
        Check if auto-start is enabled for the given app by inspecting ~/.profile.
        """
        try:
            if not os.path.exists(PROFILE_FILE):
                return False

            with open(PROFILE_FILE, 'r') as f:
                profile_lines = f.readlines()

            for line in profile_lines:
                if f'python3 "{os.path.join(WEB_APPS_DIR, app_name, "app.py")}" &' in line:
                    return True

            return False
        except Exception as e:
            print(f"Error checking auto-start for {app_name}: {e}")
            return False


def check_path():
    current_path = os.environ.get('PATH', '')
    paths = current_path.split(os.pathsep)
    if CLI_APPS_DIR not in paths:
        print(f"Warning: {CLI_APPS_DIR} is not in your PATH.")
        print("Add to ~/.profile:")
        print(f'export PATH="{CLI_APPS_DIR}:$PATH"')

    if WEB_APPS_DIR not in paths:
        print(f"Note: {WEB_APPS_DIR} is not in your PATH.")
        print("If you want to run web apps from command line, consider adding:")
        print(f'export PATH="{WEB_APPS_DIR}:$PATH"')

if __name__ == '__main__':
    check_path()
    print("Starting optimized Task Manager...")
    print("Performance improvements:")
    print("- Caching network requests (5min TTL)")
    print("- Timeout handling (10s timeout)")
    print("- Optimized pidin usage")
    print("- Lazy loading for available apps")
    
    with socketserver.TCPServer(("", PORT), TaskManagerHandler) as httpd:
        print(f"Serving on port {PORT}")
        httpd.serve_forever() 