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, base_dir=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 = base_dir or 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...")
        # Use apt-get with non-interactive flags to prevent hanging on prompts
        # Note: pkexec doesn't pass ENV vars easily, so we set them inside the shell command
        install_cmd = (
            "export DEBIAN_FRONTEND=noninteractive && "
            "apt-get update && "
            "apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' "
            "apache2 mariadb-server php libapache2-mod-php php-mysql"
        )
        
        cmd = ['pkexec', 'sh', '-c', install_cmd]
        
        try:
            self.set_progress(0.2)
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
            if process.stdout:
                for line in process.stdout:
                    self.log(line.strip())
            process.wait()
            
            if process.returncode == 0:
                self.log("Core stack installed successfully!")
                self.set_progress(0.8)
                self.log("Attempting to install phpMyAdmin (may require manual config later)...")
                # Install phpmyadmin separately as it's the most likely to cause issues
                pma_cmd = ['pkexec', 'sh', '-c', "export DEBIAN_FRONTEND=noninteractive && apt-get install -y phpmyadmin"]
                subprocess.run(pma_cmd) 
                self.log("Installation process finished.")
                self.set_progress(1.0)
            else:
                self.log(f"Installation failed with exit code {process.returncode}.")
                self.log("Tip: Try running 'sudo apt update' manually in a terminal.")
        except Exception as e:
            self.log(f"Critical Installer Error: {e}")

    def _install_windows(self):
        self.log("Windows Auto-Install - Starting binary downloads...")
        
        # Structure: (Name, URL, FolderName, ExecPathRelativeToExtractDir)
        stack = [
            ("PHP", "https://downloads.php.net/~windows/releases/archives/php-8.2.30-src.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)
            
        # Post-install configuration
        self.log("Finalizing configuration...")
        self.configure_service("apache", detected_paths.get("apache"))
        self.configure_service("php", detected_paths.get("php"))
        self.configure_service("mariadb", detected_paths.get("mariadb"))

        self.log("Windows Stack Installation Complete!")
        if self.status_cb: self.status_cb("DONE", detected_paths)

    def configure_service(self, name, exe_path):
        """Perform post-extraction setup to ensure service runs perfectly."""
        if not exe_path or not os.path.exists(exe_path):
            return

        if name == "apache" and self.os_type == "Windows":
            try:
                # Find httpd.conf
                bin_dir = os.path.dirname(exe_path)
                apa_base = os.path.dirname(bin_dir)
                conf_path = os.path.join(apa_base, "conf", "httpd.conf")
                
                if os.path.exists(conf_path):
                    self.log(f"Configuring Apache SRVROOT in {conf_path}...")
                    with open(conf_path, 'r') as f:
                        lines = f.readlines()
                    
                    with open(conf_path, 'w') as f:
                        for line in lines:
                            # Re-map SRVROOT to current absolute path
                            if line.strip().startswith('Define SRVROOT'):
                                f.write(f'Define SRVROOT "{apa_base.replace("\\", "/")}"\n')
                            else:
                                f.write(line)
            except Exception as e:
                self.log(f"Apache Config Error: {e}")

        if name == "php" and self.os_type == "Windows":
            try:
                php_dir = os.path.dirname(exe_path)
                ini_prod = os.path.join(php_dir, "php.ini-development")
                ini_path = os.path.join(php_dir, "php.ini")
                if not os.path.exists(ini_path) and os.path.exists(ini_prod):
                    shutil.copy(ini_prod, ini_path)
                    self.log("Created php.ini from template.")
            except Exception as e:
                self.log(f"PHP Config Error: {e}")

    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:
            if not os.path.exists(dest):
                os.makedirs(dest, exist_ok=True)
            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
