
import tkinter as tk
from tkinter import ttk, messagebox, font
import psutil
import threading
import locale
import webbrowser

# --- Internationalization (i18n) ---
STRINGS = {
    "en": {
        "title": "Open Port Killer",
        "pid": "PID",
        "name": "Process Name",
        "port": "Port",
        "ip": "IP Address",
        "refresh": "Refresh",
        "kill": "Kill Selected Process",
        "loading": "Loading process list...",
        "searching": "Searching for processes...",
        "found": "{count} processes with open ports found.",
        "error_fetch": "Error fetching processes: {error}",
        "no_selection_title": "No Selection",
        "no_selection_msg": "Please select a process from the list.",
        "confirm_kill_title": "Confirm Kill",
        "confirm_kill_msg": "Do you really want to kill the selected processes?",
        "error_title": "Error",
        "no_such_process": "Process with PID {pid} not found anymore.",
        "access_denied": "Permission denied to kill process with PID {pid}. Try running the script as an administrator/root.",
        "unexpected_error": "An unexpected error occurred: {error}",
        "help": "Help",
        "about": "About",
        "language": "Language",
        "about_title": "About Open Port Killer",
        "about_copyright": "Copyright © 2025 by Robin Oliver Lucas",
        "about_homepage": "Homepage:",
        "about_email": "Email:",
    },
    "de": {
        "title": "Open Port Killer",
        "pid": "PID",
        "name": "Programmname",
        "port": "Port",
        "ip": "IP-Adresse",
        "refresh": "Aktualisieren",
        "kill": "Ausgewählten Prozess beenden",
        "loading": "Lade Prozessliste...",
        "searching": "Suche nach Prozessen...",
        "found": "{count} Prozesse mit offenen Ports gefunden.",
        "error_fetch": "Fehler beim Abrufen der Prozesse: {error}",
        "no_selection_title": "Keine Auswahl",
        "no_selection_msg": "Bitte wählen Sie einen Prozess aus der Liste aus.",
        "confirm_kill_title": "Bestätigen",
        "confirm_kill_msg": "Möchten Sie die ausgewählten Prozesse wirklich beenden?",
        "error_title": "Fehler",
        "no_such_process": "Prozess mit PID {pid} nicht mehr gefunden.",
        "access_denied": "Keine Berechtigung, den Prozess mit PID {pid} zu beenden. Versuchen Sie, das Skript als Administrator/root auszuführen.",
        "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten: {error}",
        "help": "Hilfe",
        "about": "Über",
        "language": "Sprache",
        "about_title": "Über Open Port Killer",
        "about_copyright": "Copyright © 2025 by Robin Oliver Lucas",
        "about_homepage": "Homepage:",
        "about_email": "E-Mail:",
    }
}

class PortKillerApp:
    def __init__(self, root):
        self.root = root
        self.process_data = []
        self.sort_column = "port"
        self.sort_reverse = False
        self.strings = STRINGS["en"] # Default to English

        self.root.geometry("650x450")

        # --- Style & Fonts ---
        self.style = ttk.Style()
        self.style.theme_use("clam")
        self.link_font = font.Font(family="Helvetica", size=10, underline=True)

        # --- Menu ---
        self.menu = tk.Menu(self.root)
        self.root.config(menu=self.menu)
        
        # Language Menu
        self.lang_menu = tk.Menu(self.menu, tearoff=0)
        self.menu.add_cascade(label=self.strings["language"], menu=self.lang_menu)
        self.lang_menu.add_command(label="English", command=lambda: self.switch_language("en"))
        self.lang_menu.add_command(label="Deutsch", command=lambda: self.switch_language("de"))

        # Help Menu
        self.help_menu = tk.Menu(self.menu, tearoff=0)
        self.menu.add_cascade(label=self.strings["help"], menu=self.help_menu)
        self.help_menu.add_command(label=self.strings["about"], command=self.show_about_window)

        # --- Widgets ---
        self.tree_frame = ttk.Frame(self.root)
        self.tree_frame.pack(pady=10, padx=10, fill="both", expand=True)

        self.columns = ("pid", "name", "port", "ip")
        self.tree = ttk.Treeview(self.tree_frame, columns=self.columns, show="headings")
        
        self.tree.column("pid", width=80, anchor="e")
        self.tree.column("name", width=220)
        self.tree.column("port", width=80, anchor="e")
        self.tree.column("ip", width=150)

        self.tree.pack(side="left", fill="both", expand=True)

        self.scrollbar = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscroll=self.scrollbar.set)
        self.scrollbar.pack(side="right", fill="y")

        self.button_frame = ttk.Frame(self.root)
        self.button_frame.pack(pady=10)

        self.refresh_button = ttk.Button(self.button_frame, command=self.start_refresh_thread)
        self.refresh_button.pack(side="left", padx=5)

        self.kill_button = ttk.Button(self.button_frame, command=self.kill_selected_process)
        self.kill_button.pack(side="left", padx=5)
        
        self.status_label = ttk.Label(self.root)
        self.status_label.pack(pady=5)

        self.retranslate_ui()
        self.start_refresh_thread()

    def switch_language(self, lang_code):
        self.strings = STRINGS.get(lang_code, STRINGS["en"])
        self.retranslate_ui()

    def retranslate_ui(self):
        self.root.title(self.strings["title"])

        # --- Recreate Menu on language change ---
        self.menu.delete(0, "end") # Clear existing menu
        
        # Language Menu
        self.lang_menu = tk.Menu(self.menu, tearoff=0)
        self.menu.add_cascade(label=self.strings["language"], menu=self.lang_menu)
        self.lang_menu.add_command(label="English", command=lambda: self.switch_language("en"))
        self.lang_menu.add_command(label="Deutsch", command=lambda: self.switch_language("de"))

        # Help Menu
        self.help_menu = tk.Menu(self.menu, tearoff=0)
        self.menu.add_cascade(label=self.strings["help"], menu=self.help_menu)
        self.help_menu.add_command(label=self.strings["about"], command=self.show_about_window)

        # --- Update other widgets ---
        for col in self.columns:
            self.tree.heading(col, text=self.strings[col], command=lambda _col=col: self.sort_by_column(_col))
        
        self.refresh_button.config(text=self.strings["refresh"])
        self.kill_button.config(text=self.strings["kill"])
        self.status_label.config(text=self.strings["loading"])
        self.update_ui()

    def show_about_window(self):
        about_win = tk.Toplevel(self.root)
        about_win.title(self.strings["about_title"])
        about_win.geometry("350x150")
        about_win.resizable(False, False)
        about_win.transient(self.root)

        ttk.Label(about_win, text=self.strings["about_copyright"], font=("Helvetica", 10, "bold")).pack(pady=(15, 5))
        
        hp_frame = ttk.Frame(about_win)
        hp_frame.pack()
        ttk.Label(hp_frame, text=self.strings["about_homepage"]).pack(side="left")
        hp_link = tk.Label(hp_frame, text="https://rl-dev.de", fg="blue", cursor="hand2", font=self.link_font)
        hp_link.pack(side="left")
        hp_link.bind("<Button-1>", lambda e: webbrowser.open_new_tab("https://rl-dev.de"))

        email_frame = ttk.Frame(about_win)
        email_frame.pack()
        ttk.Label(email_frame, text=self.strings["about_email"]).pack(side="left")
        email_link = tk.Label(email_frame, text="admin@rl-dev.de", fg="blue", cursor="hand2", font=self.link_font)
        email_link.pack(side="left")
        email_link.bind("<Button-1>", lambda e: webbrowser.open_new_tab("mailto:admin@rl-dev.de"))

        ttk.Button(about_win, text="OK", command=about_win.destroy).pack(pady=15)

    def sort_by_column(self, col):
        if self.sort_column == col:
            self.sort_reverse = not self.sort_reverse
        else:
            self.sort_column = col
            self.sort_reverse = False
        self.update_ui()

    def start_refresh_thread(self):
        self.refresh_button.config(state="disabled")
        self.kill_button.config(state="disabled")
        self.status_label.config(text=self.strings["searching"])
        
        thread = threading.Thread(target=self.refresh_processes)
        thread.daemon = True
        thread.start()

    def refresh_processes(self):
        processes = []
        try:
            connections = psutil.net_connections(kind='inet')
            for conn in connections:
                if conn.status == 'LISTEN' and conn.pid is not None:
                    try:
                        proc = psutil.Process(conn.pid)
                        processes.append({"pid": conn.pid, "name": proc.name(), "port": conn.laddr.port, "ip": conn.laddr.ip})
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue
            self.process_data = processes
        except Exception as e:
            self.process_data = []
            self.root.after(0, lambda: messagebox.showerror(self.strings["error_title"], self.strings["error_fetch"].format(error=e)))
        self.root.after(0, self.update_ui)

    def update_ui(self):
        for i in self.tree.get_children():
            self.tree.delete(i)

        if self.process_data:
            key_func = lambda p: p.get(self.sort_column, 0) if self.sort_column in ["pid", "port"] else p.get(self.sort_column, "").lower()
            sorted_data = sorted(self.process_data, key=key_func, reverse=self.sort_reverse)
            for proc in sorted_data:
                self.tree.insert("", "end", values=(proc["pid"], proc["name"], proc["port"], proc["ip"]))
        
        self.refresh_button.config(state="normal")
        self.kill_button.config(state="normal")
        self.status_label.config(text=self.strings["found"].format(count=len(self.process_data)))

    def kill_selected_process(self):
        selected_items = self.tree.selection()
        if not selected_items:
            messagebox.showwarning(self.strings["no_selection_title"], self.strings["no_selection_msg"])
            return

        if not messagebox.askyesno(self.strings["confirm_kill_title"], self.strings["confirm_kill_msg"]):
            return

        for selected_item in selected_items:
            item = self.tree.item(selected_item)
            pid = item["values"][0]
            try:
                psutil.Process(pid).kill()
            except psutil.NoSuchProcess:
                messagebox.showerror(self.strings["error_title"], self.strings["no_such_process"].format(pid=pid))
            except psutil.AccessDenied:
                messagebox.showerror(self.strings["error_title"], self.strings["access_denied"].format(pid=pid))
            except Exception as e:
                messagebox.showerror(self.strings["error_title"], self.strings["unexpected_error"].format(error=e))

        self.start_refresh_thread()

if __name__ == "__main__":
    root = tk.Tk()
    app = PortKillerApp(root)
    root.mainloop()
