import os
import subprocess
import platform
import threading
import urllib.request
import zipfile
import shutil

class InstallerManager:
    def __init__(self, status_callback=None, progress_callback=None):
        self.status_cb = status_callback
        self.progress_cb = progress_callback
        self.os_type = platform.system()
        # Local paths for portable installs on Windows
        self.base_dir = os.path.join(os.path.expanduser("~"), ".pyser_data")
        self.bin_dir = os.path.join(self.base_dir, "bin")
        if not os.path.exists(self.bin_dir):
            os.makedirs(self.bin_dir, exist_ok=True)

    def log(self, msg):
        if self.status_cb: self.status_cb(msg)

    def set_progress(self, value):
        if self.progress_cb: self.progress_cb(value)

    def install_stack(self):
        if self.os_type == "Linux":
            threading.Thread(target=self._install_linux).start()
        elif self.os_type == "Windows":
            threading.Thread(target=self._install_windows).start()
        else:
            self.log("OS not supported for auto-install.")

    def _install_linux(self):
        self.log("Starting Linux LAMP installation...")
        cmd = [
            'pkexec', 'apt', 'install', '-y', 
            'apache2', 'mariadb-server', 'php', 
            'libapache2-mod-php', 'php-mysql', 'phpmyadmin'
        ]
        try:
            self.set_progress(0.2)
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
            for line in process.stdout:
                self.log(line.strip())
            process.wait()
            if process.returncode == 0:
                self.log("Installation successful!")
                self.set_progress(1.0)
            else:
                self.log("Installation failed. Check terminal for details.")
        except Exception as e:
            self.log(f"Error: {e}")

    def _install_windows(self):
        self.log("Windows Auto-Install - Starting binary downloads...")
        
        # Structure: (Name, URL, FolderName, ExecPathRelativeToExtractDir)
        stack = [
            ("PHP", "https://windows.php.net/downloads/releases/php-8.4.4-Win32-vs17-x64.zip", "php", "php.exe"),
            ("Apache", "https://www.apachelounge.com/download/VS17/binaries/httpd-2.4.63-250210-win64-VS17.zip", "apache", "Apache24/bin/httpd.exe"),
            ("MariaDB", "https://archive.mariadb.org/mariadb-11.4.2/winx64-packages/mariadb-11.4.2-winx64.zip", "mariadb", "bin/mariadbd.exe")
        ]
        
        detected_paths = {}
        total = len(stack)
        
        for i, (name, url, folder, exe_rel) in enumerate(stack):
            dest_zip = os.path.join(self.base_dir, f"{folder}.zip")
            extract_to = os.path.join(self.bin_dir, folder)
            
            self.log(f"Downloading {name}...")
            if not self._download_file(url, dest_zip, i/total): return
            
            self.log(f"Extracting {name}...")
            if not self._unzip_file(dest_zip, extract_to): return
            
            # Record detected path
            full_exe = os.path.join(extract_to, exe_rel)
            # Handle possible nested folders (like MariaDB often has)
            if not os.path.exists(full_exe):
                # Look one level deeper if exe not found
                for d in os.listdir(extract_to):
                    nested = os.path.join(extract_to, d, exe_rel)
                    if os.path.exists(nested):
                        full_exe = nested
                        break
            
            detected_paths[name.lower()] = full_exe
            os.remove(dest_zip)
            self.set_progress((i + 1) / total)
            
        self.log("Windows Stack Installation Complete!")
        if self.status_cb: self.status_cb("DONE", detected_paths)

    def _download_file(self, url, dest, current_progress):
        try:
            def report(block_num, block_size, total_size):
                if total_size > 0:
                    read = block_num * block_size
                    percent = read / total_size
                    # We only influence a fraction of the total progress
                    if self.progress_cb:
                        self.progress_cb(current_progress + (percent / 3)) 

            urllib.request.urlretrieve(url, dest, reporthook=report)
            return True
        except Exception as e:
            self.log(f"Download Error: {e}")
            return False

    def _unzip_file(self, zip_path, dest):
        try:
            with zipfile.ZipFile(zip_path, 'r') as zip_ref:
                zip_ref.extractall(dest)
            return True
        except Exception as e:
            self.log(f"Unzip Error: {e}")
            return False
