import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pathlib import Path
from typing import List
import tkinter.simpledialog as simpledialog

from ..config import profiles as profiles_cfg
from ..config.languages import LANGUAGE_EXTENSIONS


class ProfileManager(tk.Toplevel):
    """Dialog window to create/edit/delete scanning profiles.

    Usage: pm = ProfileManager(root); pm.wait_window()
    """

    COMMON_LANGS = ["Python", "C", "C++", "Java", "JavaScript", "HTML", "Shell"]
    # sensible defaults for ignore patterns covering common build/artifact dirs and files
    DEFAULT_IGNORES = [
        "__pycache__",
        "*.pyc",
        "*.pyo",
        "*.pyd",
        ".Python",
        "env",
        "venv",
        ".venv",
        "build",
        "dist",
        "*.egg-info",
        ".eggs",
        "node_modules",
        ".git",
        ".hg",
        ".svn",
        ".idea",
        ".vscode",
        ".DS_Store",
        "*.class",
        "*.o",
        "*.so",
        "*.dylib",
        ".pytest_cache",
        ".mypy_cache",
        ".cache",
        "coverage",
        ".tox",
        "pip-wheel-metadata",
    ]

    def __init__(self, parent, on_change=None):
        super().__init__(parent)
        self.title("Profile Manager")
        self.geometry("1100x700")
        self.on_change = on_change

        self.profiles = profiles_cfg.load_profiles()

        # Left: list of profiles
        left = ttk.Frame(self)
        left.pack(side="left", fill="y", padx=8, pady=8)
        # Make listbox taller so names are easily visible
        self.listbox = tk.Listbox(left, width=30, height=20)
        self.listbox.pack(fill="y", expand=True)
        for p in self.profiles:
            self.listbox.insert("end", p.get("name"))
        self.listbox.bind("<<ListboxSelect>>", self._on_select)

        # Right: edit form
        right = ttk.Frame(self)
        right.pack(side="left", fill="both", expand=True, padx=8, pady=8)

        ttk.Label(right, text="Profile name:").grid(row=0, column=0, sticky="w", pady=4)
        self.name_var = tk.StringVar()
        ttk.Entry(right, textvariable=self.name_var, width=48).grid(row=0, column=1, columnspan=2, sticky="ew", pady=4)

        # Create two titled frames side-by-side: one for Paths and one for Extensions
        container = ttk.Frame(right)
        container.grid(row=2, column=0, columnspan=3, sticky="nsew", pady=2)

        # Left labeled frame for Paths
        self.paths_frame = ttk.LabelFrame(container, text="Paths (files/folders):")
        self.paths_frame.grid(row=0, column=0, sticky="nsew", padx=(0,8))
        # Right labeled frame for Extensions
        self.ext_frame = ttk.LabelFrame(container, text="Filter Extensions:")
        self.ext_frame.grid(row=0, column=1, sticky="nsew")

        # Paths listbox + scrollbar inside its frame
        self.paths_listbox = tk.Listbox(self.paths_frame, height=12)
        self.paths_listbox.grid(row=0, column=0, sticky="nsew")
        paths_vsb = ttk.Scrollbar(self.paths_frame, orient="vertical", command=self.paths_listbox.yview)
        paths_vsb.grid(row=0, column=1, sticky="ns")
        self.paths_listbox.configure(yscrollcommand=paths_vsb.set)

        # Buttons for paths inside paths_frame
        btn_w = 14
        paths_btn_frame = ttk.Frame(self.paths_frame)
        paths_btn_frame.grid(row=1, column=0, columnspan=2, sticky="w", pady=(8,0))
        ttk.Button(paths_btn_frame, text="Add Folder", command=self._add_folder, width=btn_w).grid(row=0, column=0, padx=6, pady=4)
        ttk.Button(paths_btn_frame, text="Add File(s)", command=self._add_files, width=btn_w).grid(row=0, column=1, padx=6, pady=4)
        ttk.Button(paths_btn_frame, text="Edit Selected", command=self._edit_selected_path, width=btn_w).grid(row=0, column=2, padx=6, pady=4)
        ttk.Button(paths_btn_frame, text="Remove Selected", command=self._remove_selected_path, width=btn_w).grid(row=0, column=3, padx=6, pady=4)
        ttk.Button(paths_btn_frame, text="Move Up", command=self._move_selected_up, width=btn_w).grid(row=1, column=0, padx=6, pady=4)
        ttk.Button(paths_btn_frame, text="Move Down", command=self._move_selected_down, width=btn_w).grid(row=1, column=1, padx=6, pady=4)

        # Extensions listbox + scrollbar inside its frame
        self.ext_listbox = tk.Listbox(self.ext_frame, selectmode="extended", height=12)
        self.ext_listbox.grid(row=0, column=0, sticky="nsew")
        ext_vsb = ttk.Scrollbar(self.ext_frame, orient="vertical", command=self.ext_listbox.yview)
        ext_vsb.grid(row=0, column=1, sticky="ns")
        self.ext_listbox.configure(yscrollcommand=ext_vsb.set)
        # Clear selection button inside ext_frame
        ttk.Button(self.ext_frame, text="Clear Selection", command=self._clear_ext_selection, width=12).grid(row=1, column=0, pady=(8,0))

        # layout weights so both boxes have same height and paths take most width
        container.columnconfigure(0, weight=4)
        container.columnconfigure(1, weight=1)
        container.rowconfigure(0, weight=1)
        self.paths_frame.columnconfigure(0, weight=1)
        self.paths_frame.rowconfigure(0, weight=1)
        self.ext_frame.columnconfigure(0, weight=1)
        self.ext_frame.rowconfigure(0, weight=1)

        # (languages selection is handled by the Filter Extensions listbox at right)

        # Custom languages: label above large text box spanning full width
        ttk.Label(right, text="Custom languages (comma-separated):").grid(row=4, column=0, sticky="w", pady=(8, 2))
        self.custom_text = tk.Text(right, height=3, wrap="none")
        self.custom_text.grid(row=5, column=0, columnspan=3, sticky="nsew", pady=2)

        # Ignore patterns: label above large text box with scrollbars
        ttk.Label(right, text="Ignore patterns (comma-separated):").grid(row=6, column=0, sticky="w", pady=(8, 2))
        ignore_container = ttk.Frame(right)
        ignore_container.grid(row=7, column=0, columnspan=3, sticky="nsew", pady=2)
        # Text widget with both vertical and horizontal scrollbars so long patterns are readable
        self.ignore_text = tk.Text(ignore_container, height=4, wrap="none")
        self.ignore_text.grid(row=0, column=0, sticky="nsew")
        vsb_ignore = ttk.Scrollbar(ignore_container, orient="vertical", command=self.ignore_text.yview)
        vsb_ignore.grid(row=0, column=1, sticky="ns")
        hsb_ignore = ttk.Scrollbar(ignore_container, orient="horizontal", command=self.ignore_text.xview)
        hsb_ignore.grid(row=1, column=0, columnspan=2, sticky="ew")
        self.ignore_text.configure(yscrollcommand=vsb_ignore.set, xscrollcommand=hsb_ignore.set)
        ignore_container.columnconfigure(0, weight=1)
        ignore_container.rowconfigure(0, weight=1)

        # Buttons (place below the large edit boxes)
        btn_frame = ttk.Frame(right)
        btn_frame.grid(row=8, column=0, columnspan=3, pady=(12, 0))
        ttk.Button(btn_frame, text="New", command=self._new).grid(row=0, column=0, padx=4)
        ttk.Button(btn_frame, text="Save", command=self._save).grid(row=0, column=1, padx=4)
        ttk.Button(btn_frame, text="Delete", command=self._delete).grid(row=0, column=2, padx=4)
        ttk.Button(btn_frame, text="Close", command=self.destroy).grid(row=0, column=3, padx=4)

        right.columnconfigure(1, weight=1)

        # If no profiles exist, prefill form with sensible defaults (widgets are ready)
        if not self.profiles:
            self._new()
        # populate extension listbox
        try:
            self._populate_ext_listbox()
        except Exception:
            pass

    def _browse_path(self):
        d = filedialog.askdirectory()
        if d:
            # convenience: add as a path entry
            self.paths_listbox.insert("end", str(Path(d)))

    def _add_folder(self):
        d = filedialog.askdirectory()
        if d:
            self._insert_path_display("end", str(Path(d)))

    def _add_files(self):
        files = filedialog.askopenfilenames()
        for f in files:
            if f:
                self._insert_path_display("end", str(Path(f)))

    def _populate_ext_listbox(self):
        # Populate extension listbox with LANGUAGE_EXTENSIONS entries
        try:
            from ..config.languages import LANGUAGE_EXTENSIONS
        except Exception:
            return
        self.ext_listbox.delete(0, 'end')
        keys = sorted(LANGUAGE_EXTENSIONS.keys(), key=lambda s: s.lower())
        for k in keys:
            exts = LANGUAGE_EXTENSIONS.get(k) or []
            exts_label = f" ({', '.join(exts)})" if exts else ""
            self.ext_listbox.insert('end', f"{k}{exts_label}")

    def _clear_ext_selection(self):
        self.ext_listbox.selection_clear(0, 'end')

    def _edit_selected_path(self):
        sel = self.paths_listbox.curselection()
        if not sel:
            return
        idx = sel[0]
        current = self.paths_listbox.get(idx)
        # strip prefix for editing
        stripped = self._strip_display_prefix(current)
        new = simpledialog.askstring("Edit Path", "Path:", initialvalue=stripped, parent=self)
        if new:
            self.paths_listbox.delete(idx)
            self._insert_path_display(idx, new)

    def _remove_selected_path(self):
        sel = list(self.paths_listbox.curselection())
        # remove from end to start to keep indices valid
        for i in reversed(sel):
            self.paths_listbox.delete(i)

    def _move_selected_up(self):
        sel = list(self.paths_listbox.curselection())
        if not sel:
            return
        for i in sel:
            if i == 0:
                continue
            txt = self.paths_listbox.get(i)
            above = self.paths_listbox.get(i - 1)
            self.paths_listbox.delete(i - 1, i)
            self.paths_listbox.insert(i - 1, txt)
            self.paths_listbox.insert(i, above)
        # restore selection
        self.paths_listbox.selection_clear(0, 'end')
        for i in [max(0, x - 1) for x in sel]:
            self.paths_listbox.selection_set(i)

    def _move_selected_down(self):
        sel = list(self.paths_listbox.curselection())
        if not sel:
            return
        size = self.paths_listbox.size()
        for i in reversed(sel):
            if i >= size - 1:
                continue
            txt = self.paths_listbox.get(i)
            below = self.paths_listbox.get(i + 1)
            self.paths_listbox.delete(i, i + 1)
            self.paths_listbox.insert(i, below)
            self.paths_listbox.insert(i + 1, txt)
        # restore selection
        self.paths_listbox.selection_clear(0, 'end')
        for i in [min(size - 1, x + 1) for x in sel]:
            self.paths_listbox.selection_set(i)

    def _insert_path_display(self, index, path_str: str):
        # choose a prefix depending on whether path is dir/file/unknown
        p = Path(path_str)
        if p.exists():
            prefix = "[D] " if p.is_dir() else "[F] "
        else:
            prefix = "[?] "
        display = f"{prefix}{path_str}"
        self.paths_listbox.insert(index, display)

    def _strip_display_prefix(self, display_text: str) -> str:
        # remove known prefixes like [D] , [F] , [?] if present
        if display_text.startswith("[D] ") or display_text.startswith("[F] ") or display_text.startswith("[?] "):
            return display_text[4:]
        return display_text

    def _get_paths_from_listbox(self) -> List[str]:
        out = []
        for i in range(self.paths_listbox.size()):
            raw = self._strip_display_prefix(self.paths_listbox.get(i))
            out.append(raw)
        return out

    def _on_select(self, _evt=None):
        sel = self.listbox.curselection()
        if not sel:
            return
        idx = sel[0]
        pr = self.profiles[idx]
        self._load_profile(pr)

    def _load_profile(self, pr):
        self.name_var.set(pr.get("name", ""))
        # Load only new-style 'paths' list (no legacy compatibility)
        self.paths_listbox.delete(0, "end")
        paths = pr.get("paths") or []
        for p in paths:
            self._insert_path_display("end", str(p))
        langs = pr.get("languages", []) or []
        # set ext_listbox selection according to languages saved in profile
        try:
            self.ext_listbox.selection_clear(0, 'end')
            keys = [self.ext_listbox.get(i).split(' (', 1)[0] for i in range(self.ext_listbox.size())]
            for i, name in enumerate(keys):
                if name in langs:
                    self.ext_listbox.selection_set(i)
            custom = ",".join([l for l in langs if l not in keys])
        except Exception:
            custom = ",".join([l for l in langs if l not in self.COMMON_LANGS])
        # populate custom_text (large edit box)
        self.custom_text.delete("1.0", "end")
        if custom:
            self.custom_text.insert("1.0", custom)
        # populate ignore_text
        self.ignore_text.delete("1.0", "end")
        ignores = ",".join(pr.get("ignore", []))
        if ignores:
            self.ignore_text.insert("1.0", ignores)

    def _new(self):
        self.name_var.set("")
        self.paths_listbox.delete(0, "end")
        try:
            self.ext_listbox.selection_clear(0, 'end')
        except Exception:
            pass
        self.custom_text.delete("1.0", "end")
        self.ignore_text.delete("1.0", "end")
        # insert the DEFAULT_IGNORES joined by commas
        self.ignore_text.insert("1.0", ",".join(self.DEFAULT_IGNORES))

    def _save(self):
        name = self.name_var.get().strip()
        if not name:
            messagebox.showwarning("Validation", "Profile must have a name.")
            return
        # collect paths from listbox
        paths = self._get_paths_from_listbox()
        # validation: if no paths, ask confirmation
        if not paths:
            if not messagebox.askyesno("Confirm", "Paths list is empty. Save profile anyway?"):
                return
        # ensure at least one of the paths exists on filesystem
        existing = [p for p in paths if Path(p).exists()]
        if not existing and paths:
            messagebox.showwarning("Validation", "None of the configured paths exist. Please add at least one existing path.")
            return
        # languages come from extension listbox selection
        langs: List[str] = []
        try:
            sel = [self.ext_listbox.get(i) for i in self.ext_listbox.curselection()]
            for s in sel:
                # s is like 'Python (.py, .pyw)'
                lang_name = s.split(' (', 1)[0]
                if lang_name and lang_name not in langs:
                    langs.append(lang_name)
        except Exception:
            pass
        raw_custom = self.custom_text.get("1.0", "end").strip()
        raw_custom = raw_custom.replace("\n", ",")
        custom = [c.strip() for c in raw_custom.split(",") if c.strip()]
        langs.extend([c for c in custom if c])
        raw_ignore = self.ignore_text.get("1.0", "end").strip()
        raw_ignore = raw_ignore.replace("\n", ",")
        ignore = [s.strip() for s in raw_ignore.split(",") if s.strip()]
        profile = {"name": name, "paths": paths, "languages": langs, "ignore": ignore}
        profiles_cfg.add_or_update_profile(profile)
        self.profiles = profiles_cfg.load_profiles()
        # refresh listbox
        self.listbox.delete(0, "end")
        for p in self.profiles:
            self.listbox.insert("end", p.get("name"))
        messagebox.showinfo("Saved", f"Profile '{name}' saved.")
        if self.on_change:
            self.on_change()

    def _delete(self):
        name = self.name_var.get().strip()
        if not name:
            return
        if not messagebox.askyesno("Delete", f"Delete profile '{name}'?"):
            return
        profiles_cfg.delete_profile(name)
        self.profiles = profiles_cfg.load_profiles()
        self.listbox.delete(0, "end")
        for p in self.profiles:
            self.listbox.insert("end", p.get("name"))
        self._new()
        if self.on_change:
            self.on_change()
