import os
import platform
import json
import threading
import time
import re
from .constants import APP_NAME, VERSION, COLORS, DEVELOPER
from .services import ServiceManager
from .utils import install_dependencies, find_executable
from .installer import InstallerManager

# Run installer on import/start
install_dependencies()

import customtkinter as ctk

class App(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.title(f"{APP_NAME} {VERSION}")
        self.geometry("1100x850")
        self.configure(fg_color=COLORS["bg"])
        
        self.os_type = platform.system()
        self.config_path = os.path.join(os.path.expanduser("~"), ".pyser_config.json")
        
        self.settings = self.get_defaults()
        self.load_config()
        self.auto_detect_binaries()

        ctk.set_appearance_mode("Dark")
        ctk.set_default_color_theme("blue")

        # Runtime State
        self.py_server_process = None
        self.py_server_dir = os.path.expanduser("~")
        self.ftp_server_process = None
        self.ftp_dir = os.path.expanduser("~")
        self.active_log_thread = None
        self.stop_log_streaming = threading.Event()
        self.pma_installed = False

        self.setup_ui()
        self.update_status_loop()
        self.update_tool_info_loop()

    def get_defaults(self):
        defaults = {
            "http_port": 8000,
            "ftp_port": 2121,
            "ftp_user": "user",
            "ftp_pass": "12345",
            "php_path": "",
            "perl_path": "",
            "apache_svc": "apache2",
            "mariadb_svc": "mariadb",
            "apache_bin": "",
            "mariadb_bin": "",
            "apache_conf": "/etc/apache2/sites-available/000-default.conf",
            "apache_webroot": "/var/www/html",
            "apache_logs": "/var/log/apache2/error.log"
        }
        
        if self.os_type == "Windows":
            defaults.update({
                "apache_svc": "apache2.4",
                "mariadb_svc": "mysql",
                "apache_conf": "C:\\xampp\\apache\\conf\\httpd.conf",
                "apache_webroot": "C:\\xampp\\htdocs",
                "apache_logs": "C:\\xampp\\apache\\logs\\error.log"
            })
        elif self.os_type == "Darwin":
            defaults.update({
                "apache_svc": "org.apache.httpd",
                "mariadb_svc": "homebrew.mxcl.mariadb",
                "apache_conf": "/etc/apache2/httpd.conf",
                "apache_webroot": "/Library/WebServer/Documents",
                "apache_logs": "/var/log/apache2/error_log"
            })
        return defaults

    def auto_detect_binaries(self):
        """Proactively find common paths if not set."""
        if not self.settings["php_path"]:
            self.settings["php_path"] = self.find_executable("php") or ("C:\\xampp\\php\\php.exe" if self.os_type == "Windows" else "")
        if not self.settings["perl_path"]:
            self.settings["perl_path"] = self.find_executable("perl") or ("C:\\xampp\\perl\\bin\\perl.exe" if self.os_type == "Windows" else "")
        if not self.settings["apache_bin"]:
            self.settings["apache_bin"] = self.find_executable("apache2") or self.find_executable("httpd")
        if not self.settings["mariadb_bin"]:
            self.settings["mariadb_bin"] = self.find_executable("mariadb") or self.find_executable("mysqld")
        
        # Detect phpMyAdmin
        pma_paths = ["/usr/share/phpmyadmin", "/var/www/html/phpmyadmin", "/etc/phpmyadmin"]
        self.pma_installed = any(os.path.exists(p) for p in pma_paths)

    def find_executable(self, name):
        return find_executable(name, self.os_type)

    def load_config(self):
        if os.path.exists(self.config_path):
            try:
                with open(self.config_path, "r") as f:
                    saved = json.load(f)
                    self.settings.update(saved)
            except Exception as e:
                print(f"Error loading config: {e}")

    def save_config(self):
        try:
            with open(self.config_path, "w") as f:
                json.dump(self.settings, f, indent=4)
        except Exception as e:
            messagebox.showerror("Error", f"Could not save settings: {e}")

    def setup_ui(self):
        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # --- Sidebar ---
        self.sidebar = ctk.CTkFrame(self, width=220, corner_radius=0, fg_color=COLORS["sidebar"], border_width=0)
        self.sidebar.grid(row=0, column=0, sticky="nsew")
        
        self.logo_label = ctk.CTkLabel(self.sidebar, text="⚡ PyServer", font=ctk.CTkFont(size=24, weight="bold"))
        self.logo_label.pack(pady=30, padx=20)

        self.dash_btn = self.create_nav_btn("Dashboard", "🏠", lambda: self.show_view("dashboard"))
        self.logs_btn = self.create_nav_btn("Live Logs", "📜", lambda: self.show_view("logs"))
        self.settings_btn = self.create_nav_btn("Settings", "⚙️", lambda: self.show_view("settings"))
        self.about_btn = self.create_nav_btn("About", "👤", lambda: self.show_view("about"))
        self.inst_btn = self.create_nav_btn("Stack Installer", "🛠️", lambda: self.show_view("installer"))
        
        # Tool info box in sidebar
        self.tool_info_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent")
        self.tool_info_frame.pack(side="bottom", fill="x", padx=20, pady=20)
        
        self.php_info_lbl = ctk.CTkLabel(self.tool_info_frame, text="PHP: scanning...", font=ctk.CTkFont(size=11), text_color=COLORS["text_dim"])
        self.php_info_lbl.pack(anchor="w")
        self.perl_info_lbl = ctk.CTkLabel(self.tool_info_frame, text="Perl: scanning...", font=ctk.CTkFont(size=11), text_color=COLORS["text_dim"])
        self.perl_info_lbl.pack(anchor="w")
        
        self.os_info = ctk.CTkLabel(self.tool_info_frame, text=f"Platform: {self.os_type}", font=ctk.CTkFont(size=10), text_color="#555")
        self.os_info.pack(anchor="w", pady=(10, 0))
        
        self.dev_info = ctk.CTkLabel(self.tool_info_frame, text=f"Developed by {DEVELOPER}", font=ctk.CTkFont(size=10, weight="bold"), text_color=COLORS["accent"])
        self.dev_info.pack(anchor="w", pady=(5, 0))

        # --- Main Container ---
        self.container = ctk.CTkFrame(self, fg_color="transparent")
        self.container.grid(row=0, column=1, sticky="nsew", padx=30, pady=30)
        self.container.grid_columnconfigure(0, weight=1)
        self.container.grid_rowconfigure(0, weight=1)

        self.views = {}
        self.setup_dashboard()
        self.setup_logs_view()
        self.setup_settings()
        self.setup_about_view()
        self.setup_installer_view()
        self.show_view("dashboard")

    def create_nav_btn(self, text, icon, command):
        btn = ctk.CTkButton(self.sidebar, text=f"{icon}  {text}", fg_color="transparent", 
                           text_color="white", hover_color="#2c2c2c", anchor="w", 
                           font=ctk.CTkFont(size=14), height=45, command=command)
        btn.pack(fill="x", padx=15, pady=2)
        return btn

    def show_view(self, name):
        for view in self.views.values():
            view.grid_remove()
        self.views[name].grid(row=0, column=0, sticky="nsew")
        
        # Handle log streaming activation
        if name == "logs":
            self.start_log_streaming()
        else:
            self.stop_log_streaming.set()

    def setup_dashboard(self):
        view = ctk.CTkScrollableFrame(self.container, fg_color="transparent")
        self.views["dashboard"] = view

        title_frame = ctk.CTkFrame(view, fg_color="transparent")
        title_frame.pack(pady=(0, 30), fill="x")
        
        ctk.CTkLabel(title_frame, text="System Dashboard", font=ctk.CTkFont(size=28, weight="bold")).pack(side="left")
        
        # System Health Widget
        self.health_card = ctk.CTkFrame(title_frame, fg_color="#1e293b", corner_radius=10, height=40)
        self.health_card.pack(side="right")
        self.health_lbl = ctk.CTkLabel(self.health_card, text="SYSTEM HEALTH: EXCELLENT", font=ctk.CTkFont(size=11, weight="bold"), text_color=COLORS["success"])
        self.health_lbl.pack(padx=15, pady=5)

        # Layout: 2 Columns for cards
        self.cards_frame = ctk.CTkFrame(view, fg_color="transparent")
        self.cards_frame.pack(fill="x")
        self.cards_frame.grid_columnconfigure((0, 1), weight=1, pad=20)

        self.service_cards = {}
        self.url_labels = {}
        
        # Apache & MySQL
        self.create_premium_card(self.cards_frame, 0, 0, "Apache Web Server", self.settings["apache_svc"], "🌐", self.handle_apache_start)
        self.create_premium_card(self.cards_frame, 0, 1, "MariaDB / MySQL", self.settings["mariadb_svc"], "🗄️")
        
        # Python Internal Servers
        self.create_python_server_card(view)
        self.create_ftp_server_card(view)

    def create_premium_card(self, parent, row, col, name, svc_id, icon, start_cmd=None):
        card = ctk.CTkFrame(parent, fg_color=COLORS["card"], corner_radius=15, border_width=1, border_color="#333")
        card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew")
        
        # Header
        header = ctk.CTkFrame(card, fg_color="transparent")
        header.pack(fill="x", padx=20, pady=(20, 10))
        
        ctk.CTkLabel(header, text=icon, font=ctk.CTkFont(size=24)).pack(side="left")
        ctk.CTkLabel(header, text=name, font=ctk.CTkFont(size=18, weight="bold")).pack(side="left", padx=10)
        
        status_dot = ctk.CTkLabel(header, text="●", font=ctk.CTkFont(size=20))
        status_dot.pack(side="right")
        
        status_lbl = ctk.CTkLabel(header, text="CHECKING", font=ctk.CTkFont(size=12, weight="bold"))
        status_lbl.pack(side="right", padx=5)
        
        self.service_cards[svc_id] = (status_lbl, status_dot)

        # Info
        info_frame = ctk.CTkFrame(card, fg_color="#1a1a1a", corner_radius=8)
        info_frame.pack(fill="x", padx=20, pady=10)
        
        if "Apache" in name:
            self.apa_root_lbl = ctk.CTkLabel(info_frame, text=f"Root: {self.settings['apache_webroot']}", font=ctk.CTkFont(size=11), text_color=COLORS["text_dim"])
            self.apa_root_lbl.pack(pady=10, padx=10, anchor="w")
            
            self.url_labels["apache"] = ctk.CTkLabel(card, text="", text_color=COLORS["accent"], font=ctk.CTkFont(size=12, underline=True), cursor="hand2")
            self.url_labels["apache"].pack(pady=(0, 10), padx=20, anchor="w")
            self.url_labels["apache"].bind("<Button-1>", lambda e: self.open_url("http://localhost"))
        elif "MariaDB" in name:
            ctk.CTkLabel(info_frame, text=f"Service ID: {svc_id}", font=ctk.CTkFont(size=11), text_color=COLORS["text_dim"]).pack(pady=10, padx=10, anchor="w")
            
            self.url_labels["pma"] = ctk.CTkLabel(card, text="", text_color=COLORS["accent"], font=ctk.CTkFont(size=12, underline=True), cursor="hand2")
            self.url_labels["pma"].pack(pady=(0, 10), padx=20, anchor="w")
            self.url_labels["pma"].bind("<Button-1>", lambda e: self.open_url("http://localhost/phpmyadmin"))
        else:
            ctk.CTkLabel(info_frame, text=f"Service ID: {svc_id}", font=ctk.CTkFont(size=11), text_color=COLORS["text_dim"]).pack(pady=10, padx=10, anchor="w")

        # Actions
        actions = ctk.CTkFrame(card, fg_color="transparent")
        actions.pack(fill="x", padx=20, pady=(10, 20))
        
        start_func = start_cmd if start_cmd else lambda: ServiceManager.run_command(svc_id, "start")
        ctk.CTkButton(actions, text="Start", width=100, height=35, fg_color=COLORS["success"], hover_color="#16a34a", command=start_func).pack(side="left", padx=(0, 10))
        ctk.CTkButton(actions, text="Stop", width=60, height=35, fg_color="transparent", border_width=1, border_color=COLORS["danger"], text_color=COLORS["danger"], command=lambda: ServiceManager.run_command(svc_id, "stop")).pack(side="left")
        
        if "Apache" in name:
            ctk.CTkButton(actions, text="Fix", width=40, height=35, fg_color="#333", text_color=COLORS["warning"], command=self.show_apache_fix_menu).pack(side="right")
            ctk.CTkButton(actions, text="Root", width=40, height=35, fg_color="#333", command=self.update_apache_root).pack(side="right", padx=5)

    def create_python_server_card(self, parent):
        card = ctk.CTkFrame(parent, fg_color=COLORS["card"], corner_radius=15, border_width=1, border_color="#333")
        card.pack(fill="x", pady=20)

        header = ctk.CTkFrame(card, fg_color="transparent")
        header.pack(fill="x", padx=20, pady=(20, 10))
        ctk.CTkLabel(header, text="🐍 Python HTTP Web Server", font=ctk.CTkFont(size=18, weight="bold")).pack(side="left")
        
        self.py_status_lbl = ctk.CTkLabel(header, text="STOPPED", font=ctk.CTkFont(size=12, weight="bold"), text_color=COLORS["danger"])
        self.py_status_lbl.pack(side="right")

        body = ctk.CTkFrame(card, fg_color="transparent")
        body.pack(fill="x", padx=20, pady=10)
        
        self.py_info_lbl = ctk.CTkLabel(body, text=f"Directory: {self.py_server_dir}  |  Port: {self.settings['http_port']}", font=ctk.CTkFont(size=13), text_color=COLORS["text_dim"])
        self.py_info_lbl.pack(side="left")
        
        self.py_url_lbl = ctk.CTkLabel(card, text="", text_color=COLORS["accent"], font=ctk.CTkFont(size=12, underline=True), cursor="hand2")
        self.py_url_lbl.pack(pady=(0, 10), padx=20, anchor="w")
        self.py_url_lbl.bind("<Button-1>", lambda e: self.open_url(f"http://localhost:{self.settings['http_port']}"))

        footer = ctk.CTkFrame(card, fg_color="transparent")
        footer.pack(fill="x", padx=20, pady=(0, 20))
        
        self.py_toggle_btn = ctk.CTkButton(footer, text="Start Server", fg_color=COLORS["accent"], height=35, command=self.toggle_python_server)
        self.py_toggle_btn.pack(side="left", padx=(0, 10))
        ctk.CTkButton(footer, text="Change Directory", fg_color="#333", height=35, command=self.select_folder).pack(side="left")

    def create_ftp_server_card(self, parent):
        card = ctk.CTkFrame(parent, fg_color=COLORS["card"], corner_radius=15, border_width=1, border_color="#333")
        card.pack(fill="x", pady=20)

        header = ctk.CTkFrame(card, fg_color="transparent")
        header.pack(fill="x", padx=20, pady=(20, 10))
        ctk.CTkLabel(header, text="📂 Python FTP Server", font=ctk.CTkFont(size=18, weight="bold")).pack(side="left")
        
        self.ftp_status_lbl = ctk.CTkLabel(header, text="STOPPED", font=ctk.CTkFont(size=12, weight="bold"), text_color=COLORS["danger"])
        self.ftp_status_lbl.pack(side="right")

        body = ctk.CTkFrame(card, fg_color="transparent")
        body.pack(fill="x", padx=20, pady=10)
        self.ftp_info_lbl = ctk.CTkLabel(body, text=f"User: {self.settings['ftp_user']}  |  Dir: {self.ftp_dir}  |  Port: {self.settings['ftp_port']}", font=ctk.CTkFont(size=13), text_color=COLORS["text_dim"])
        self.ftp_info_lbl.pack(side="left")
        
        self.ftp_url_lbl = ctk.CTkLabel(card, text="", text_color=COLORS["accent"], font=ctk.CTkFont(size=12, underline=True), cursor="hand2")
        self.ftp_url_lbl.pack(pady=(0, 10), padx=20, anchor="w")
        self.ftp_url_lbl.bind("<Button-1>", lambda e: self.open_url(f"ftp://{self.settings['ftp_user']}@localhost:{self.settings['ftp_port']}"))

        footer = ctk.CTkFrame(card, fg_color="transparent")
        footer.pack(fill="x", padx=20, pady=(0, 20))
        
        self.ftp_toggle_btn = ctk.CTkButton(footer, text="Start FTP", fg_color=COLORS["accent"], height=35, command=self.toggle_ftp_server)
        self.ftp_toggle_btn.pack(side="left", padx=(0, 10))
        ctk.CTkButton(footer, text="Change Directory", fg_color="#333", height=35, command=self.select_ftp_folder).pack(side="left")

    def setup_logs_view(self):
        view = ctk.CTkFrame(self.container, fg_color="transparent")
        self.views["logs"] = view
        
        ctk.CTkLabel(view, text="Live Server Logs", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=(0, 10), anchor="w")
        ctk.CTkLabel(view, text="Streaming from Apache error log...", font=ctk.CTkFont(size=12), text_color=COLORS["text_dim"]).pack(pady=(0, 20), anchor="w")
        
        self.log_textbox = ctk.CTkTextbox(view, bg_color="transparent", fg_color="#000", font=("Courier", 12), text_color="#0f0")
        self.log_textbox.pack(fill="both", expand=True)

    def setup_settings(self):
        view = ctk.CTkScrollableFrame(self.container, fg_color="transparent")
        self.views["settings"] = view

        ctk.CTkLabel(view, text="Advanced Settings", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=(0, 20), anchor="w")

        # Config sections
        self.create_settings_field(view, "Network", [
            ("HTTP Port", "http_port", "int"),
            ("FTP Port", "ftp_port", "int")
        ])
        
        self.create_settings_field(view, "FTP Credentials", [
            ("Username", "ftp_user", "str"),
            ("Password", "ftp_pass", "str", True)
        ])
        
        self.create_settings_field(view, "OS Service IDs", [
            ("Apache Service", "apache_svc", "str"),
            ("MariaDB Service", "mariadb_svc", "str")
        ])
        
        self.create_settings_field(view, "Paths & Config", [
            ("Apache Config", "apache_conf", "str"),
            ("Apache Log Path", "apache_logs", "str"),
            ("PHP Binary", "php_path", "str"),
            ("Perl Binary", "perl_path", "str")
        ])

        save_btn = ctk.CTkButton(view, text="Apply Changes", fg_color=COLORS["success"], font=ctk.CTkFont(weight="bold"), height=40, command=self.apply_settings)
        save_btn.pack(pady=40, fill="x")

    def setup_about_view(self):
        view = ctk.CTkFrame(self.container, fg_color="transparent")
        self.views["about"] = view
        
        # Profile Section
        profile_frame = ctk.CTkFrame(view, fg_color=COLORS["card"], corner_radius=20)
        profile_frame.pack(pady=20, fill="x")
        
        # Giant Name Label (Avatar placeholder)
        avatar = ctk.CTkFrame(profile_frame, width=80, height=80, corner_radius=40, fg_color=COLORS["accent"])
        avatar.pack(pady=(30, 10))
        ctk.CTkLabel(avatar, text=DEVELOPER[0].upper(), font=ctk.CTkFont(size=40, weight="bold"), text_color="white").place(relx=0.5, rely=0.5, anchor="center")
        
        ctk.CTkLabel(profile_frame, text=DEVELOPER, font=ctk.CTkFont(size=24, weight="bold")).pack()
        ctk.CTkLabel(profile_frame, text="Creator & Lead Developer", font=ctk.CTkFont(size=14), text_color=COLORS["text_dim"]).pack(pady=(0, 30))
        
        # Project Info
        desc_frame = ctk.CTkFrame(view, fg_color="transparent")
        desc_frame.pack(pady=20, fill="both", expand=True)
        
        desc_text = (
            f"PyServer v{VERSION} is a professional-grade server control panel "
            "designed specifically for Python developers who need a robust, "
            "cross-platform environment similar to XAMPP but for the modern era.\n\n"
            "This project features native UAC elevation, systemctl integration, "
            "and a premium UI/UX experience powered by CustomTkinter."
        )
        
        ctk.CTkLabel(desc_frame, text="About the Project", font=ctk.CTkFont(size=18, weight="bold")).pack(anchor="w")
        desc_lbl = ctk.CTkLabel(desc_frame, text=desc_text, font=ctk.CTkFont(size=13), justify="left", wraplength=700)
        desc_lbl.pack(pady=10, anchor="w")
        
        # Links
        links_frame = ctk.CTkFrame(view, fg_color="transparent")
        links_frame.pack(pady=20, fill="x")
        
        github_btn = ctk.CTkButton(links_frame, text="GitHub Profile", fg_color="#333", width=150, height=40, command=lambda: self.open_url(f"https://github.com/{DEVELOPER}"))
        github_btn.pack(side="left", padx=10)
        
        web_btn = ctk.CTkButton(links_frame, text="Project Website", fg_color="#333", width=150, height=40, command=lambda: self.open_url("https://pyserver.io"))
        web_btn.pack(side="left", padx=10)

    def setup_installer_view(self):
        view = ctk.CTkFrame(self.container, fg_color="transparent")
        self.views["installer"] = view
        
        ctk.CTkLabel(view, text="⚡ Stack Installer", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=(0, 10), anchor="w")
        ctk.CTkLabel(view, text="Install Apache, PHP, and MariaDB automatically.", font=ctk.CTkFont(size=13), text_color=COLORS["text_dim"]).pack(pady=(0, 30), anchor="w")
        
        # Installer Progress
        self.inst_card = ctk.CTkFrame(view, fg_color=COLORS["card"], corner_radius=15, border_width=1, border_color="#333")
        self.inst_card.pack(fill="x", pady=10)
        
        self.inst_status_lbl = ctk.CTkLabel(self.inst_card, text="Status: Ready", font=ctk.CTkFont(size=12, weight="bold"))
        self.inst_status_lbl.pack(pady=(20, 10), padx=20, anchor="w")
        
        self.inst_progress = ctk.CTkProgressBar(self.inst_card, height=15, corner_radius=10, fg_color="#18181b", progress_color=COLORS["accent"])
        self.inst_progress.pack(fill="x", padx=20, pady=10)
        self.inst_progress.set(0)
        
        self.inst_log = ctk.CTkTextbox(view, height=200, fg_color="#000", font=("Courier", 11), text_color="#0f0")
        self.inst_log.pack(fill="x", pady=20)
        
        self.inst_start_btn = ctk.CTkButton(view, text="Start Full Installation", height=45, fg_color=COLORS["success"], font=ctk.CTkFont(weight="bold"), command=self.run_stack_installer)
        self.inst_start_btn.pack(pady=20, fill="x")

    def run_stack_installer(self):
        if not messagebox.askyesno("Confirm", "This will download and install server components. Proceed?"):
            return
            
        self.inst_start_btn.configure(state="disabled")
        self.inst_log.delete("1.0", "end")
        
        def on_status(msg, data=None):
            if msg == "DONE" and data:
                # Update settings with new paths
                if "php" in data: self.settings["php_path"] = data["php"]
                if "apache" in data: self.settings["apache_bin"] = data["apache"]
                if "mariadb" in data: self.settings["mariadb_bin"] = data["mariadb"]
                
                # Special: Update Apache log and conf paths for portable install
                if self.os_type == "Windows" and "apache" in data:
                    apa_base = os.path.dirname(os.path.dirname(data["apache"]))
                    self.settings["apache_conf"] = os.path.join(apa_base, "conf", "httpd.conf")
                    self.settings["apache_logs"] = os.path.join(apa_base, "logs", "error.log")
                    self.settings["apache_webroot"] = os.path.join(apa_base, "htdocs")

                self.save_config()
                self.inst_status_lbl.configure(text="Status: Installation Complete!")
                return

            self.inst_status_lbl.configure(text=f"Status: {msg[:40]}...")
            self.inst_log.insert("end", f"> {msg}\n")
            self.inst_log.see("end")
            
        def on_progress(val):
            self.inst_progress.set(val)
            if val >= 1.0:
                self.inst_start_btn.configure(state="normal")
                messagebox.showinfo("Success", "Stack installation complete! Please check your settings.")

        self.installer = InstallerManager(status_callback=on_status, progress_callback=on_progress)
        self.installer.install_stack()

    def create_settings_field(self, parent, title, fields):
        group = ctk.CTkFrame(parent, fg_color=COLORS["card"], corner_radius=10, border_width=1, border_color="#333")
        group.pack(fill="x", pady=10)
        
        ctk.CTkLabel(group, text=title, font=ctk.CTkFont(size=14, weight="bold"), text_color=COLORS["accent"]).pack(padx=20, pady=(15, 10), anchor="w")
        
        self.settings_entries = getattr(self, "settings_entries", {})
        
        for label, key, dtype, *secret in fields:
            f = ctk.CTkFrame(group, fg_color="transparent")
            f.pack(fill="x", padx=20, pady=5)
            ctk.CTkLabel(f, text=label, width=150, anchor="w").pack(side="left")
            
            show_char = "*" if secret and secret[0] else None
            entry = ctk.CTkEntry(f, fg_color="#18181b", border_color="#333", show=show_char)
            entry.insert(0, str(self.settings[key]))
            entry.pack(side="right", fill="x", expand=True, padx=(10, 0))
            self.settings_entries[key] = (entry, dtype)

    def apply_settings(self):
        try:
            for key, (entry, dtype) in self.settings_entries.items():
                val = entry.get()
                self.settings[key] = int(val) if dtype == "int" else val
            
            self.save_config()
            self.apa_root_lbl.configure(text=f"Root: {self.settings['apache_webroot']}")
            self.py_info_lbl.configure(text=f"Directory: {self.py_server_dir}  |  Port: {self.settings['http_port']}")
            self.ftp_info_lbl.configure(text=f"User: {self.settings['ftp_user']}  |  Dir: {self.ftp_dir}  |  Port: {self.settings['ftp_port']}")
            
            messagebox.showinfo("Success", "Settings saved successfully.")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to apply: {e}")

    # --- Live Log Streaming ---
    def start_log_streaming(self):
        self.stop_log_streaming.clear()
        if self.active_log_thread and self.active_log_thread.is_alive():
            return
            
        self.active_log_thread = threading.Thread(target=self._stream_logs, daemon=True)
        self.active_log_thread.start()

    def _stream_logs(self):
        log_path = self.settings["apache_logs"]
        try:
            with open(log_path, 'r') as f:
                f.seek(0, os.SEEK_END)
                while not self.stop_log_streaming.is_set():
                    line = f.readline()
                    if not line:
                        time.sleep(0.5)
                        continue
                    self.log_textbox.insert("end", line)
                    self.log_textbox.see("end")
                    if int(self.log_textbox.index('end-1c').split('.')[0]) > 500:
                        self.log_textbox.delete("1.0", "2.0")
        except:
            self.log_textbox.insert("end", "Error: Could not access log file. Check permissions or path in Settings.\n")

    # --- Functional Handlers ---
    def handle_apache_start(self):
        ServiceManager.run_command(self.settings["apache_svc"], "start", callback=self.on_apache_start_result)

    def on_apache_start_result(self, success):
        if not success:
            if messagebox.askyesno("Startup Failed", "Apache failed to start.\nOpen troubleshooting?"):
                self.show_apache_fix_menu()

    def open_url(self, url):
        import webbrowser
        webbrowser.open(url)

    def toggle_python_server(self):
        if self.py_server_process:
            self.py_server_process.terminate()
            self.py_server_process = None
            self.py_toggle_btn.configure(text="Start Server", fg_color=COLORS["accent"])
        else:
            try:
                cmd = ["python3", "-m", "http.server", str(self.settings["http_port"])]
                self.py_server_process = subprocess.Popen(cmd, cwd=self.py_server_dir)
                self.py_toggle_btn.configure(text="Stop", fg_color=COLORS["danger"])
                self.open_url(f"http://localhost:{self.settings['http_port']}")
            except Exception as e:
                messagebox.showerror("Error", str(e))

    def toggle_ftp_server(self):
        if self.ftp_server_process:
            self.ftp_server_process.terminate()
            self.ftp_server_process = None
            self.ftp_toggle_btn.configure(text="Start FTP", fg_color=COLORS["accent"])
        else:
            try:
                cmd = ["python3", "-m", "pyftpdlib", "-p", str(self.settings["ftp_port"]), "-d", self.ftp_dir, "-u", self.settings["ftp_user"], "-P", self.settings["ftp_pass"]]
                self.ftp_server_process = subprocess.Popen(cmd)
                self.ftp_toggle_btn.configure(text="Stop", fg_color=COLORS["danger"])
            except Exception as e:
                messagebox.showerror("Error", str(e))

    def select_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.py_server_dir = folder
            self.py_info_lbl.configure(text=f"Directory: {self.py_server_dir}  |  Port: {self.settings['http_port']}")

    def select_ftp_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.ftp_dir = folder
            self.ftp_info_lbl.configure(text=f"User: {self.settings['ftp_user']}  |  Dir: {self.ftp_dir}  |  Port: {self.settings['ftp_port']}")

    def update_apache_root(self):
        new_dir = filedialog.askdirectory()
        if not new_dir: return
        try:
            conf_path = self.settings['apache_conf']
            if self.os_type == "Linux":
                tmp_conf = "/tmp/pycon_apache_conf"
                subprocess.run(["cp", conf_path, tmp_conf])
                with open(tmp_conf, 'r') as f: content = f.read()
                content = re.sub(r'DocumentRoot\s+["\']?.*["\']?', f'DocumentRoot "{new_dir}"', content)
                content = content.replace(f'<Directory "{self.settings["apache_webroot"]}">', f'<Directory "{new_dir}">')
                with open(tmp_conf, 'w') as f: f.write(content)
                subprocess.run(["pkexec", "cp", tmp_conf, conf_path])
            else:
                with open(conf_path, 'r') as f: content = f.read()
                content = re.sub(r'DocumentRoot\s+["\']?.*["\']?', f'DocumentRoot "{new_dir}"', content)
                with open(conf_path, 'w') as f: f.write(content)

            self.settings['apache_webroot'] = new_dir
            self.save_config()
            self.apa_root_lbl.configure(text=f"Root: {new_dir}")
            messagebox.showinfo("Success", "Web root updated.")
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def show_apache_fix_menu(self):
        fix_win = ctk.CTkToplevel(self)
        fix_win.title("Apache Fix Toolkit")
        fix_win.geometry("400x320")
        ctk.CTkLabel(fix_win, text="🔧 Troubleshooting", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=20)
        ctk.CTkButton(fix_win, text="Fix ServerName (FQDN)", height=40, command=self.fix_apache_fqdn).pack(pady=5, padx=30, fill="x")
        ctk.CTkButton(fix_win, text="Fix Home Permissions (Linux)", height=40, command=self.fix_apache_permissions).pack(pady=5, padx=30, fill="x")
        ctk.CTkButton(fix_win, text="Enable phpMyAdmin Web Access", height=40, command=self.fix_phpmyadmin).pack(pady=5, padx=30, fill="x")
        ctk.CTkButton(fix_win, text="Fix 403 Forbidden Errors", height=40, command=self.fix_forbidden_error).pack(pady=5, padx=30, fill="x")
        ctk.CTkButton(fix_win, text="Run Deep Diagnostics", height=40, fg_color=COLORS["accent"], command=self.run_deep_diagnostics).pack(pady=20, padx=30, fill="x")

    def fix_forbidden_error(self):
        try:
            web_root = self.settings['apache_webroot']
            conf_path = self.settings['apache_conf']
            
            if self.os_type == "Linux":
                subprocess.run(["pkexec", "chmod", "o+rx", web_root])
                parent = os.path.dirname(web_root)
                while parent and parent != "/":
                    subprocess.run(["pkexec", "chmod", "o+x", parent])
                    parent = os.path.dirname(parent)
            
            if self.os_type == "Linux":
                tmp_conf = "/tmp/pycon_forbidden_fix"
                subprocess.run(["cp", conf_path, tmp_conf])
                with open(tmp_conf, 'r') as f:
                    content = f.read()
                
                pattern = rf'<Directory\s+["\']?{re.escape(web_root)}["\']?>.*?</Directory>'
                match = re.search(pattern, content, re.DOTALL)
                
                if match:
                    block = match.group(0)
                    if "Require all granted" not in block:
                        new_block = block.replace("</Directory>", "    Require all granted\n</Directory>")
                        content = content.replace(block, new_block)
                else:
                    content += f"\n<Directory \"{web_root}\">\n    Options Indexes FollowSymLinks\n    AllowOverride All\n    Require all granted\n</Directory>\n"
                
                with open(tmp_conf, 'w') as f:
                    f.write(content)
                subprocess.run(["pkexec", "cp", tmp_conf, conf_path])
            
            messagebox.showinfo("Success", "Applied permission and configuration fixes. Please restart Apache.")
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def run_deep_diagnostics(self):
        results = []
        is_linux = self.os_type == "Linux"
        
        import socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        port_80_open = sock.connect_ex(('127.0.0.1', 80)) == 0
        sock.close()
        
        if port_80_open:
            results.append("✅ Port 80 is reachable.")
        else:
            results.append("❌ Port 80 is NOT responding (Connection Refused).")
            results.append("   - Ensure Apache is actually Running.")

        try:
            php_out = subprocess.check_output([self.settings['php_path'], "-m"], text=True)
            if "mysqli" in php_out:
                results.append("✅ PHP MySQL extension is loaded.")
            else:
                results.append("❌ PHP 'mysqli' extension is MISSING.")
                if is_linux:
                    results.append("   - Fix: Run 'sudo apt install php-mysql' then restart Apache.")
        except:
            results.append("⚠️ Could not verify PHP modules.")

        if is_linux:
            if os.path.exists("/usr/share/phpmyadmin"):
                results.append("✅ phpMyAdmin files found.")
            else:
                results.append("❌ phpMyAdmin package might not be installed.")

        report_win = ctk.CTkToplevel(self)
        report_win.title("Diagnostic Report")
        report_win.geometry("500x400")
        txt = ctk.CTkTextbox(report_win, font=("Courier", 12))
        txt.pack(fill="both", expand=True, padx=20, pady=20)
        txt.insert("0.0", "\n".join(results))
        txt.configure(state="disabled")

    def fix_phpmyadmin(self):
        if self.os_type != "Linux":
            messagebox.showinfo("Info", "This fix is specific to Linux/Debian installations.")
            return
        try:
            conf_link = "/etc/apache2/conf-enabled/phpmyadmin.conf"
            conf_source = "/etc/phpmyadmin/apache.conf"
            
            if not os.path.exists(conf_link):
                if os.path.exists(conf_source):
                    subprocess.run(["pkexec", "ln", "-s", conf_source, conf_link])
                    messagebox.showinfo("Success", "phpMyAdmin configuration link created. Please restart Apache.")
                else:
                    messagebox.showerror("Error", "phpMyAdmin config source not found.")
            else:
                messagebox.showinfo("Info", "phpMyAdmin configuration is already enabled.")
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def fix_apache_fqdn(self):
        try:
            if self.os_type == "Linux":
                subprocess.run(f"echo 'ServerName localhost' | pkexec tee -a /etc/apache2/apache2.conf", shell=True)
            elif self.os_type == "Windows":
                with open(self.settings['apache_conf'], 'a') as f: f.write("\nServerName localhost:80\n")
            messagebox.showinfo("Success", "FQDN Fix applied.")
        except Exception as e: messagebox.showerror("Error", str(e))

    def fix_apache_permissions(self):
        if self.os_type != "Linux": return
        user_path = "/".join(self.settings['apache_webroot'].split("/")[:3])
        if "/home/" in user_path:
            subprocess.run(["pkexec", "chmod", "+x", user_path])
            messagebox.showinfo("Fixed", f"Chmod +x applied to {user_path}")

    def update_tool_info_loop(self):
        def loop():
            while True:
                try:
                    ver = subprocess.check_output([self.settings['php_path'], "-v"], text=True).split("\n")[0]
                    self.php_info_lbl.configure(text=f"PHP: {ver[:25]}...", text_color=COLORS["success"])
                except: self.php_info_lbl.configure(text="PHP: Not Found", text_color=COLORS["danger"])
                try:
                    ver = subprocess.check_output([self.settings['perl_path'], "-v"], text=True).split("\n")[1].strip()
                    self.perl_info_lbl.configure(text=f"Perl: {ver[:25]}...", text_color=COLORS["success"])
                except: self.perl_info_lbl.configure(text="Perl: Not Found", text_color=COLORS["danger"])
                time.sleep(10)
        threading.Thread(target=loop, daemon=True).start()

    def update_status_loop(self):
        def loop():
            while True:
                active_count = 0
                total_services = 0
                for svc_id, (lbl, dot) in self.service_cards.items():
                    total_services += 1
                    status = ServiceManager.get_status(svc_id)
                    color = COLORS["success"] if status == "active" else COLORS["danger"] if status == "inactive" else COLORS["text_dim"]
                    if status == "active": active_count += 1
                    try:
                        lbl.configure(text=status.upper(), text_color=color)
                        dot.configure(text_color=color)
                        if svc_id == self.settings["apache_svc"]:
                            self.url_labels["apache"].configure(text="Visit: http://localhost" if status == "active" else "")
                        if svc_id == self.settings["mariadb_svc"]:
                            self.url_labels["pma"].configure(text="Open phpMyAdmin: http://localhost/phpmyadmin" if status == "active" else "")
                    except: pass
                try:
                    total_services += 2
                    if self.py_server_process and self.py_server_process.poll() is None:
                        active_count += 1
                        self.py_status_lbl.configure(text="RUNNING", text_color=COLORS["success"])
                        self.py_url_lbl.configure(text=f"Visit: http://localhost:{self.settings['http_port']}")
                    else:
                        self.py_status_lbl.configure(text="STOPPED", text_color=COLORS["danger"])
                        self.py_url_lbl.configure(text="")

                    if self.ftp_server_process and self.ftp_server_process.poll() is None:
                        active_count += 1
                        self.ftp_status_lbl.configure(text="RUNNING", text_color=COLORS["success"])
                        self.ftp_url_lbl.configure(text=f"Open: ftp://{self.settings['ftp_user']}@localhost:{self.settings['ftp_port']}")
                    else:
                        self.ftp_status_lbl.configure(text="STOPPED", text_color=COLORS["danger"])
                        self.ftp_url_lbl.configure(text="")
                    
                    if active_count == 0:
                        self.health_lbl.configure(text="SYSTEM HEALTH: IDLE", text_color=COLORS["text_dim"])
                    elif active_count < (total_services // 2):
                        self.health_lbl.configure(text="SYSTEM HEALTH: MODERATE", text_color=COLORS["warning"])
                    else:
                        self.health_lbl.configure(text="SYSTEM HEALTH: EXCELLENT", text_color=COLORS["success"])
                except: pass
                time.sleep(2 if active_count > 0 else 5)
        threading.Thread(target=loop, daemon=True).start()

def main():
    app = App()
    app.mainloop()

if __name__ == "__main__":
    main()
