#!/usr/bin/env python3
"""
Argus Installer Script
Downloads and decrypts Argus software from the web repository.
"""

import os
import sys
import subprocess
import argparse
import zipfile
import platform
import re
from pathlib import Path
import urllib.request
import urllib.error
from html.parser import HTMLParser

try:
    from tqdm import tqdm
except ImportError:
    tqdm = None


class LinkParser(HTMLParser):
    """Parse HTML to extract links."""
    def __init__(self):
        super().__init__()
        self.links = []
    
    def handle_starttag(self, tag, attrs):
        if tag == 'a':
            for attr, value in attrs:
                if attr == 'href':
                    self.links.append(value)


class ArgusInstaller:
    def __init__(self, base_url="https://raspberrypi.tail34e8af.ts.net/Argus/"):
        self.base_url = base_url.rstrip('/')
        self.versions = {}
        self.system_info = self.get_system_info()
    
    def get_system_info(self):
        """Get current system information using uname and system commands."""
        info = {}
        
        # Use uname to get system info
        try:
            import subprocess
            result = subprocess.run(['uname', '-a'], capture_output=True, text=True)
            if result.returncode == 0:
                uname_output = result.stdout.strip()
                parts = uname_output.split()
                info['kernel_name'] = parts[0]  # Darwin, Linux, etc.
                info['hostname'] = parts[1] if len(parts) > 1 else 'unknown'
                info['kernel_release'] = parts[2] if len(parts) > 2 else 'unknown'
                info['kernel_version'] = ' '.join(parts[3:5]) if len(parts) > 3 else 'unknown'
                info['arch'] = parts[-1]  # Last part is architecture
        except:
            # Fallback to platform module
            info['kernel_name'] = platform.system()
            info['arch'] = platform.machine()
            info['kernel_release'] = platform.release()
        
        # Get detailed OS info
        try:
            if info.get('kernel_name') == 'Darwin':
                # macOS specific info
                result = subprocess.run(['sw_vers'], capture_output=True, text=True)
                if result.returncode == 0:
                    for line in result.stdout.strip().split('\n'):
                        if 'ProductName:' in line:
                            info['os_name'] = line.split(':', 1)[1].strip()
                        elif 'ProductVersion:' in line:
                            info['os_version'] = line.split(':', 1)[1].strip()
                        elif 'BuildVersion:' in line:
                            info['build_version'] = line.split(':', 1)[1].strip()
            
            elif info.get('kernel_name') == 'Linux':
                # Linux specific info
                try:
                    with open('/etc/os-release', 'r') as f:
                        for line in f:
                            if line.startswith('PRETTY_NAME='):
                                info['os_name'] = line.split('=', 1)[1].strip().strip('"')
                            elif line.startswith('VERSION_ID='):
                                info['os_version'] = line.split('=', 1)[1].strip().strip('"')
                except:
                    info['os_name'] = 'Linux'
        except:
            pass
        
        # Get system tool versions that builders check
        info['tools'] = {}
        
        # Get curl version
        try:
            result = subprocess.run(['curl', '--version'], capture_output=True, text=True)
            if result.returncode == 0:
                info['tools']['curl'] = result.stdout.split('\n')[0]
        except:
            pass
        
        # Get OpenSSL version
        try:
            result = subprocess.run(['openssl', 'version'], capture_output=True, text=True)
            if result.returncode == 0:
                info['tools']['openssl'] = result.stdout.strip()
        except:
            pass
        
        return info
    
    def fetch_page(self, url):
        """Fetch content from a URL."""
        try:
            with urllib.request.urlopen(url) as response:
                return response.read().decode('utf-8')
        except urllib.error.URLError as e:
            print(f"Error fetching {url}: {e}")
            return None
    
    def parse_links(self, html_content):
        """Parse HTML to extract links."""
        parser = LinkParser()
        parser.feed(html_content)
        return parser.links
    
    def discover_versions(self):
        """Discover available versions from the repository."""
        print("Discovering available versions...")
        
        # Fetch main page
        main_content = self.fetch_page(self.base_url)
        if not main_content:
            return False
        
        links = self.parse_links(main_content)
        
        # Check for versions directory
        if 'versions/' in links:
            versions_url = f"{self.base_url}/versions/"
            versions_content = self.fetch_page(versions_url)
            if versions_content:
                version_links = self.parse_links(versions_content)
                
                for version_link in version_links:
                    if version_link.endswith('/') and version_link != '../':
                        version_name = version_link.rstrip('/')
                        version_url = f"{versions_url}{version_link}"
                        
                        # Get files in version directory
                        version_content = self.fetch_page(version_url)
                        if version_content:
                            file_links = self.parse_links(version_content)
                            
                            for file_link in file_links:
                                if file_link.endswith('.enc'):
                                    file_url = f"{version_url}{file_link}"
                                    display_name = f"{version_name}/{file_link}"
                                    self.versions[display_name] = file_url
        
        # Check for latest version
        if 'Argus-latest.zip.enc' in links:
            latest_url = f"{self.base_url}/Argus-latest.zip.enc"
            self.versions["latest"] = latest_url
        
        return len(self.versions) > 0
    
    def list_versions(self):
        """List available versions."""
        if not self.discover_versions():
            print("No versions found!")
            return
        
        print("\nAvailable versions:")
        for i, version in enumerate(sorted(self.versions.keys()), 1):
            print(f"  {i}. {version}")
    
    def download_file(self, url, filename):
        """Download a file from URL with progress bar."""
        print(f"Downloading {filename}...")
        
        try:
            # First, get the file size
            with urllib.request.urlopen(url) as response:
                meta = response.info()
                file_size = int(meta.get('Content-Length', 0))
            
            # Progress tracker class
            class ProgressTracker:
                def __init__(self, total_size, desc):
                    self.total_size = total_size
                    self.desc = desc
                    self.pbar = None
                    
                def __call__(self, block_num, block_size, total_size):
                    if tqdm and total_size > 0:
                        if self.pbar is None:
                            self.pbar = tqdm(
                                total=total_size,
                                unit='B',
                                unit_scale=True,
                                unit_divisor=1024,
                                desc=self.desc
                            )
                        self.pbar.update(block_num * block_size - self.pbar.n)
                    elif total_size > 0:
                        # Simple text progress if tqdm not available
                        percent = min(100, (block_num * block_size * 100) // total_size)
                        sys.stdout.write(f"\rDownloading: {percent}%")
                        sys.stdout.flush()
                
                def close(self):
                    if self.pbar:
                        self.pbar.close()
            
            # Create progress tracker
            progress_tracker = ProgressTracker(file_size, filename)
            
            # Download with progress
            urllib.request.urlretrieve(url, filename, progress_tracker)
            
            # Close progress bar
            progress_tracker.close()
            
            if file_size > 0 and not tqdm:
                print()  # New line after text progress
            
            print(f"Downloaded {filename}")
            return True
            
        except urllib.error.URLError as e:
            print(f"Error downloading {url}: {e}")
            return False
    
    def decrypt_file(self, encrypted_file, output_file):
        """Decrypt the downloaded file using sdist."""
        print(f"Decrypting {encrypted_file}...")
        
        try:
            cmd = ['sdist', '-c', '-p', 'NONE', '-f', 'decrypt', '-a', encrypted_file, output_file]
            result = subprocess.run(cmd, capture_output=True, text=True)
            
            # Check if output file was actually created
            if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                print(f"Successfully decrypted to {output_file}")
                return True
            else:
                if result.returncode == 0:
                    print(f"Decryption appeared to succeed but no output file found")
                    print(f"Stderr: {result.stderr}")
                    print(f"Stdout: {result.stdout}")
                else:
                    print(f"Decryption failed: {result.stderr}")
                return False
        except FileNotFoundError:
            print("Error: 'sdist' command not found. Please ensure sdist is installed and in PATH.")
            return False
        except Exception as e:
            print(f"Error during decryption: {e}")
            return False
    
    def unzip_file(self, zip_file, extract_dir):
        """Unzip the decrypted file."""
        print(f"Extracting {zip_file}...")
        
        try:
            with zipfile.ZipFile(zip_file, 'r') as zip_ref:
                zip_ref.extractall(extract_dir)
            print(f"Successfully extracted to {extract_dir}")
            return True
        except zipfile.BadZipFile:
            print("Error: Invalid zip file")
            return False
        except Exception as e:
            print(f"Error extracting zip file: {e}")
            return False
    
    def discover_builds(self, extract_dir):
        """Discover available builds from extracted zip."""
        builds = {}
        manifest_path = os.path.join(extract_dir, 'argus_server_dist', 'BUILD_MANIFEST.txt')
        
        if not os.path.exists(manifest_path):
            print("No BUILD_MANIFEST.txt found")
            return builds
        
        # Parse manifest to get build information
        try:
            with open(manifest_path, 'r') as f:
                content = f.read()
            
            # Find build sections
            build_pattern = r'--- (\w+(?:-\w+)*) ---'
            build_matches = re.findall(build_pattern, content)
            
            for build_name in build_matches:
                build_dir = os.path.join(extract_dir, 'argus_server_dist', build_name)
                if os.path.exists(build_dir):
                    builder_file = os.path.join(build_dir, 'builder.txt')
                    build_info = {'name': build_name, 'path': build_dir}
                    
                    # Parse builder.txt for detailed info
                    if os.path.exists(builder_file):
                        with open(builder_file, 'r') as f:
                            builder_content = f.read()
                        
                        # Extract OS info - handle both Linux and macOS formats
                        os_match = re.search(r'PRETTY_NAME="([^"]+)"', builder_content)
                        if os_match:
                            build_info['os'] = os_match.group(1)
                        else:
                            # Try macOS format
                            macos_match = re.search(r'ProductName:\s*(.+)', builder_content)
                            if macos_match:
                                product_name = macos_match.group(1).strip()
                                version_match = re.search(r'ProductVersion:\s*(.+)', builder_content)
                                if version_match:
                                    build_info['os'] = f"{product_name} {version_match.group(1).strip()}"
                                else:
                                    build_info['os'] = product_name
                        
                        # Extract kernel version
                        kernel_match = re.search(r'Kernel Version ===\s*\n(.+)', builder_content)
                        if kernel_match:
                            build_info['kernel'] = kernel_match.group(1).strip()
                        else:
                            # Try macOS Darwin info
                            darwin_match = re.search(r'Darwin ([\d.]+)', builder_content)
                            if darwin_match:
                                build_info['kernel'] = f"Darwin {darwin_match.group(1)}"
                        
                        # Extract architecture - handle both formats
                        arch_match = re.search(r'(\w+)\s+unknown', builder_content)
                        if arch_match:
                            build_info['arch'] = arch_match.group(1).lower()
                        else:
                            # Try macOS format
                            macos_arch_match = re.search(r'(arm64|x86_64)\s+', builder_content)
                            if macos_arch_match:
                                build_info['arch'] = macos_arch_match.group(1).lower()
                            elif 'arm64' in builder_content:
                                build_info['arch'] = 'arm64'
                            elif 'x86_64' in builder_content:
                                build_info['arch'] = 'x86_64'
                    
                    builds[build_name] = build_info
        
        except Exception as e:
            print(f"Error parsing build manifest: {e}")
        
        return builds
    
    def find_closest_build(self, builds):
        """Find the build closest to the current system using uname and builder info."""
        if not builds:
            return None
        
        current_kernel = self.system_info.get('kernel_name', '').lower()
        current_arch = self.system_info.get('arch', '').lower()
        
        best_build = None
        best_score = -1
        
        for build_name, build_info in builds.items():
            score = 0
            
            # Check kernel/darwin match
            if current_kernel == 'darwin' and 'macos' in build_name.lower():
                score += 3  # Perfect platform match
            elif current_kernel == 'linux' and 'debian' in build_name.lower():
                score += 2  # Good platform match
            
            # Check architecture match
            build_arch = build_info.get('arch', '').lower()
            if current_arch == build_arch:
                score += 2
            elif (current_arch in ['arm64', 'aarch64'] and build_arch in ['arm64', 'aarch64']):
                score += 2
            elif (current_arch in ['x86_64', 'amd64'] and build_arch in ['x86_64', 'amd64']):
                score += 2
            
            # Additional matches from build info
            build_os = build_info.get('os', '').lower()
            if 'macos' in build_os and current_kernel == 'darwin':
                score += 1
            elif 'debian' in build_os and current_kernel == 'linux':
                score += 1
            
            build_info['match_score'] = score
            if score > best_score:
                best_score = score
                best_build = build_name
        
        return best_build
    
    
    
    def install_version(self, version_key, output_dir="."):
        """Install a specific version."""
        # Ensure versions are discovered
        if not self.versions:
            self.discover_versions()
        
        if version_key not in self.versions:
            print(f"Version '{version_key}' not found!")
            print(f"Available versions: {list(self.versions.keys())}")
            return False
        
        # Create output directory if it doesn't exist
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        
        # Determine filenames
        url = self.versions[version_key]
        if version_key == "latest":
            encrypted_filename = "Argus-latest.zip.enc"
            decrypted_filename = "Argus-latest.zip"
        else:
            # Extract filename from version key
            filename = version_key.split('/')[-1]
            encrypted_filename = filename
            decrypted_filename = filename.replace('.enc', '')
        
        encrypted_path = os.path.join(output_dir, encrypted_filename)
        decrypted_path = os.path.join(output_dir, decrypted_filename)
        
        # Download and decrypt
        if not self.download_file(url, encrypted_path):
            return False
        
        if not self.decrypt_file(encrypted_path, decrypted_path):
            # Clean up encrypted file on failure
            if os.path.exists(encrypted_path):
                os.remove(encrypted_path)
            return False
        
        # Unzip the decrypted file
        if not self.unzip_file(decrypted_path, output_dir):
            print("Warning: Failed to extract zip file")
            # Don't fail the installation, just warn
        else:
            # Clean up zip file after successful extraction
            if os.path.exists(decrypted_path):
                os.remove(decrypted_path)
            
            # Discover available builds
            builds = self.discover_builds(output_dir)
            if builds:
                print(f"\nDiscovered {len(builds)} available builds:")
                
                # Find best build using uname comparison
                best_build = self.find_closest_build(builds)
                
                if builds:
                    print(f"\nDiscovered {len(builds)} available builds:")
                    
                    # Calculate match scores for all builds
                    for build_name, build_info in builds.items():
                        score = 0
                        current_kernel = self.system_info.get('kernel_name', '').lower()
                        current_arch = self.system_info.get('arch', '').lower()
                        
                        # Check platform match
                        if current_kernel == 'darwin' and 'macos' in build_name.lower():
                            score += 3
                        elif current_kernel == 'linux' and 'debian' in build_name.lower():
                            score += 2
                        
                        # Check architecture match
                        build_arch = build_info.get('arch', '').lower()
                        if current_arch == build_arch or \
                           (current_arch in ['arm64', 'aarch64'] and build_arch in ['arm64', 'aarch64']) or \
                           (current_arch in ['x86_64', 'amd64'] and build_arch in ['x86_64', 'amd64']):
                            score += 2
                        
                        build_info['match_score'] = score
                    
                    # Sort by match score
                    sorted_builds = sorted(builds.items(), 
                                          key=lambda x: x[1].get('match_score', 0), 
                                          reverse=True)
                    
                    for i, (build_name, build_info) in enumerate(sorted_builds, 1):
                        score = build_info.get('match_score', 0)
                        os_info = build_info.get('os', 'Unknown OS')
                        marker = " ⭐ CLOSEST MATCH" if score > 0 and i == 1 else ""
                        print(f"  {i}. {build_name} - {os_info}{marker}")
                
                # Show detailed comparison and let user choose
                if builds:
                    print(f"\n🎯 Closest build match: {best_build}")
                    print(f"   Platform: {builds[best_build].get('os', 'Unknown')}")
                    print(f"   Build arch: {builds[best_build].get('arch', 'Unknown')}")
                    print(f"   Path: {builds[best_build]['path']}")
                    
                    # Check if executable exists for recommended build
                    exec_path = os.path.join(builds[best_build]['path'], 'argus_server')
                    if os.path.exists(exec_path):
                        print(f"   Executable: {exec_path}")
                        print(f"   Size: {os.path.getsize(exec_path) / (1024*1024):.1f} MB")
                    
                    # Let user choose a build
                    print(f"\n📋 Available builds:")
                    sorted_builds = sorted(builds.items(), 
                                          key=lambda x: x[1].get('match_score', 0), 
                                          reverse=True)
                    
                    for i, (build_name, build_info) in enumerate(sorted_builds, 1):
                        score = build_info.get('match_score', 0)
                        os_info = build_info.get('os', 'Unknown OS')
                        marker = " ⭐ CLOSEST MATCH" if build_name == best_build else ""
                        print(f"  {i}. {build_name} - {os_info}{marker}")
                    
                    try:
                        choice = input(f"\nSelect build to install (1-{len(builds)}): ").strip()
                        if not choice.isdigit() or not 1 <= int(choice) <= len(builds):
                            print("Invalid choice! Using recommended build.")
                            selected_build = best_build
                        else:
                            selected_build = sorted_builds[int(choice) - 1][0]
                            print(f"Selected: {selected_build}")
                    except (KeyboardInterrupt, EOFError):
                        print("\nInstallation cancelled.")
                        return False
                    
                    # Let user choose installation location
                    print(f"\n📍 Where would you like to install argus_server?")
                    print(f"  1. Current directory ({output_dir})")
                    print(f"  2. /usr/local/bin/ (system-wide, requires sudo)")
                    
                    try:
                        location_choice = input("Select location (1-2): ").strip()
                        if location_choice == "2":
                            # Install to /usr/local/bin
                            install_dir = "/usr/local/bin"
                            source_exec = os.path.join(builds[selected_build]['path'], 'argus_server')
                            target_exec = os.path.join(install_dir, 'argus_server')
                            
                            if not os.path.exists(source_exec):
                                print(f"Error: Source executable not found at {source_exec}")
                                return False
                            
                            print(f"Installing {source_exec} to {target_exec}...")
                            copy_cmd = f"sudo cp '{source_exec}' '{target_exec}'"
                            result = os.system(copy_cmd)
                            
                            if result == 0:
                                # Make executable
                                chmod_cmd = f"sudo chmod +x '{target_exec}'"
                                os.system(chmod_cmd)
                                print(f"✅ Successfully installed argus_server to {target_exec}")
                                
                                # Verify installation
                                if os.path.exists(target_exec):
                                    print(f"   Size: {os.path.getsize(target_exec) / (1024*1024):.1f} MB")
                                    print(f"   You can now run 'argus_server' from anywhere")
                            else:
                                print(f"❌ Failed to install to /usr/local/bin")
                                return False
                        else:
                            # Install to current directory
                            install_dir = output_dir
                            source_exec = os.path.join(builds[selected_build]['path'], 'argus_server')
                            target_exec = os.path.join(install_dir, 'argus_server')
                            
                            if source_exec != target_exec:
                                import shutil
                                shutil.copy2(source_exec, target_exec)
                                os.chmod(target_exec, 0o755)
                            
                            print(f"✅ Successfully installed argus_server to {target_exec}")
                            if os.path.exists(target_exec):
                                print(f"   Size: {os.path.getsize(target_exec) / (1024*1024):.1f} MB")
                                print(f"   Run with: ./{target_exec}")
                    
                    except (KeyboardInterrupt, EOFError):
                        print("\nInstallation cancelled.")
                        return False
        
        # Clean up encrypted file on success
        if os.path.exists(encrypted_path):
            os.remove(encrypted_path)
        
        print(f"\nInstallation complete! Files extracted to: {output_dir}")
        return True


def main():
    # Check for tqdm and show helpful message if not available
    if tqdm is None:
        print("Note: Install tqdm for better progress bars: pip install tqdm")
    
    parser = argparse.ArgumentParser(description="Argus Installer - Download and decrypt Argus software")
    parser.add_argument("-l", "--list", action="store_true", help="List available versions")
    parser.add_argument("-v", "--version", help="Version to install (use 'latest' for latest version)")
    parser.add_argument("-o", "--output", default=".", help="Output directory (default: current directory)")
    parser.add_argument("-i", "--interactive", action="store_true", help="Interactive mode to select version")
    
    args = parser.parse_args()
    
    installer = ArgusInstaller()
    
    if args.list:
        installer.list_versions()
        return
    
    if args.interactive or not args.version:
        # Interactive mode
        if not installer.discover_versions():
            print("No versions found!")
            sys.exit(1)
        
        installer.list_versions()
        
        try:
            choice = input("\nSelect version (number): ").strip()
            if not choice.isdigit():
                print("Invalid choice!")
                sys.exit(1)
            
            choice_num = int(choice) - 1
            versions_list = sorted(installer.versions.keys())
            
            if choice_num < 0 or choice_num >= len(versions_list):
                print("Invalid choice!")
                sys.exit(1)
            
            selected_version = versions_list[choice_num]
            print(f"Selected: {selected_version}")
            
        except KeyboardInterrupt:
            print("\nInstallation cancelled.")
            sys.exit(0)
        except Exception as e:
            print(f"Error: {e}")
            sys.exit(1)
    else:
        selected_version = args.version
    
    # Install the selected version
    if installer.install_version(selected_version, args.output):
        print("Installation successful!")
    else:
        print("Installation failed!")
        sys.exit(1)


if __name__ == "__main__":
    main()

