from __future__ import annotations

import argparse
import json
import threading
import webbrowser
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from ms_bj_browser import server


HOST = "0.0.0.0"


@dataclass(slots=True)
class LauncherSettings:
    root: str = str(server.DEFAULT_ROOT)
    port: int = server.DEFAULT_PORT
    metadata_path: str = ""
    configure_firewall: bool = True
    use_https: bool = False
    certfile: str = ""
    keyfile: str = ""
    open_browser: bool = True


def launcher_settings_path() -> Path:
    return Path(__file__).resolve().parent / "data" / "launcher_settings.json"


def normalize_boolean(value: Any, default: bool = True) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return default
    if isinstance(value, str):
        return value.strip().lower() not in {"", "0", "false", "no", "off"}
    return bool(value)


def load_launcher_settings(path: Path | None = None) -> LauncherSettings:
    defaults = LauncherSettings()
    target = path or launcher_settings_path()
    if not target.exists():
        return defaults

    try:
        payload = json.loads(target.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return defaults
    if not isinstance(payload, dict):
        return defaults

    try:
        port = server.validate_port(payload.get("port", defaults.port))
    except ValueError:
        port = defaults.port

    root = str(payload.get("root") or defaults.root).strip() or defaults.root
    metadata_path = str(payload.get("metadata_path") or "").strip()
    open_browser = normalize_boolean(
        payload.get("open_browser"), default=defaults.open_browser
    )
    configure_firewall = normalize_boolean(
        payload.get("configure_firewall"), default=defaults.configure_firewall
    )
    use_https = normalize_boolean(
        payload.get("use_https"), default=defaults.use_https
    )
    return LauncherSettings(
        root=root,
        port=port,
        metadata_path=metadata_path,
        configure_firewall=configure_firewall,
        use_https=use_https,
        certfile=str(payload.get("certfile") or "").strip(),
        keyfile=str(payload.get("keyfile") or "").strip(),
        open_browser=open_browser,
    )


def save_launcher_settings(
    settings: LauncherSettings, path: Path | None = None
) -> None:
    target = path or launcher_settings_path()
    target.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "root": settings.root,
        "port": settings.port,
        "metadata_path": settings.metadata_path,
        "configure_firewall": settings.configure_firewall,
        "use_https": settings.use_https,
        "certfile": settings.certfile,
        "keyfile": settings.keyfile,
        "open_browser": settings.open_browser,
    }
    target.write_text(
        json.dumps(payload, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Launcher grafic pentru serverul manuscriselor Bolyai."
    )
    parser.add_argument(
        "--root",
        type=Path,
        default=None,
        help="Completeaza automat folderul de manuscrise in interfata grafica.",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=None,
        help="Completeaza automat portul in interfata grafica.",
    )
    parser.add_argument(
        "--metadata",
        type=Path,
        default=None,
        help="Completeaza automat fisierul JSON pentru metadate.",
    )
    parser.add_argument(
        "--https",
        action="store_true",
        help="Porneste launcherul cu HTTPS bifat.",
    )
    parser.add_argument(
        "--certfile",
        type=Path,
        default=None,
        help="Completeaza automat fisierul certificatului PEM/CRT.",
    )
    parser.add_argument(
        "--keyfile",
        type=Path,
        default=None,
        help="Completeaza automat fisierul cheii private PEM.",
    )
    parser.add_argument(
        "--no-firewall",
        action="store_true",
        help="Debifeaza configurarea automata a Windows Firewall.",
    )
    parser.add_argument(
        "--no-browser",
        action="store_true",
        help="Nu deschide automat browserul dupa pornire.",
    )
    return parser.parse_args()


def build_initial_settings(args: argparse.Namespace) -> LauncherSettings:
    settings = load_launcher_settings()
    if args.root is not None:
        settings.root = str(args.root)
    if args.port is not None:
        settings.port = server.validate_port(args.port)
    if args.metadata is not None:
        settings.metadata_path = str(args.metadata)
    if args.https:
        settings.use_https = True
        if settings.port == server.DEFAULT_PORT:
            settings.port = server.DEFAULT_HTTPS_PORT
    if args.certfile is not None:
        settings.certfile = str(args.certfile)
        settings.use_https = True
    if args.keyfile is not None:
        settings.keyfile = str(args.keyfile)
        settings.use_https = True
    if settings.use_https and settings.port == server.DEFAULT_PORT:
        settings.port = server.DEFAULT_HTTPS_PORT
    if args.no_firewall:
        settings.configure_firewall = False
    if args.no_browser:
        settings.open_browser = False
    return settings


def prompt_settings(initial: LauncherSettings) -> LauncherSettings | None:
    import tkinter as tk
    from tkinter import filedialog, messagebox, ttk

    selection: dict[str, LauncherSettings | None] = {"value": None}

    root_window = tk.Tk()
    root_window.title("MS_BJ Browser")
    root_window.resizable(False, False)
    root_window.columnconfigure(0, weight=1)

    frame = ttk.Frame(root_window, padding=18)
    frame.grid(row=0, column=0, sticky="nsew")
    frame.columnconfigure(1, weight=1)

    folder_var = tk.StringVar(value=initial.root)
    port_var = tk.StringVar(value=str(initial.port))
    metadata_var = tk.StringVar(value=initial.metadata_path)
    configure_firewall_var = tk.BooleanVar(value=initial.configure_firewall)
    use_https_var = tk.BooleanVar(value=initial.use_https)
    certfile_var = tk.StringVar(value=initial.certfile)
    keyfile_var = tk.StringVar(value=initial.keyfile)
    open_browser_var = tk.BooleanVar(value=initial.open_browser)

    def browse_root() -> None:
        selected = filedialog.askdirectory(
            title="Alege folderul cu manuscrise",
            initialdir=folder_var.get().strip() or str(server.DEFAULT_ROOT),
        )
        if selected:
            folder_var.set(selected)

    def browse_metadata() -> None:
        initial_path = metadata_var.get().strip() or str(server.default_metadata_path())
        selected = filedialog.asksaveasfilename(
            title="Alege fisierul JSON pentru metadate",
            initialfile=Path(initial_path).name,
            initialdir=str(Path(initial_path).parent),
            defaultextension=".json",
            filetypes=[("Fisiere JSON", "*.json"), ("Toate fisierele", "*.*")],
        )
        if selected:
            metadata_var.set(selected)

    def browse_certfile() -> None:
        initial_path = certfile_var.get().strip() or folder_var.get().strip() or str(
            server.DEFAULT_ROOT
        )
        selected = filedialog.askopenfilename(
            title="Alege certificatul HTTPS",
            initialdir=str(Path(initial_path).parent if Path(initial_path).suffix else Path(initial_path)),
            filetypes=[
                ("Certificate PEM/CRT/CER", "*.pem;*.crt;*.cer"),
                ("Toate fisierele", "*.*"),
            ],
        )
        if selected:
            certfile_var.set(selected)

    def browse_keyfile() -> None:
        initial_path = keyfile_var.get().strip() or certfile_var.get().strip() or folder_var.get().strip() or str(
            server.DEFAULT_ROOT
        )
        selected = filedialog.askopenfilename(
            title="Alege cheia privata HTTPS",
            initialdir=str(Path(initial_path).parent if Path(initial_path).suffix else Path(initial_path)),
            filetypes=[
                ("Chei private PEM/KEY", "*.pem;*.key"),
                ("Toate fisierele", "*.*"),
            ],
        )
        if selected:
            keyfile_var.set(selected)

    def submit() -> None:
        folder_value = folder_var.get().strip()
        metadata_value = metadata_var.get().strip()
        certfile_value = certfile_var.get().strip()
        keyfile_value = keyfile_var.get().strip()
        if not folder_value:
            messagebox.showerror(
                "Configurare invalida",
                "Alege un folder de manuscrise inainte de pornire.",
                parent=root_window,
            )
            return
        try:
            server.validate_root(Path(folder_value))
            port_value = server.validate_port(port_var.get().strip())
            if bool(use_https_var.get()):
                server.validate_tls_files(
                    Path(certfile_value) if certfile_value else None,
                    Path(keyfile_value) if keyfile_value else None,
                )
        except ValueError as exc:
            messagebox.showerror("Configurare invalida", str(exc), parent=root_window)
            return

        selection["value"] = LauncherSettings(
            root=folder_value,
            port=port_value,
            metadata_path=metadata_value,
            configure_firewall=bool(configure_firewall_var.get()),
            use_https=bool(use_https_var.get()),
            certfile=certfile_value,
            keyfile=keyfile_value,
            open_browser=bool(open_browser_var.get()),
        )
        root_window.destroy()

    def cancel() -> None:
        root_window.destroy()

    ttk.Label(frame, text="Pornire server manuscrise", font=("", 12, "bold")).grid(
        row=0, column=0, columnspan=3, sticky="w"
    )
    ttk.Label(
        frame,
        text="Serverul va asculta pe 0.0.0.0. Browserul se deschide local pe 127.0.0.1.",
        wraplength=520,
        justify="left",
    ).grid(row=1, column=0, columnspan=3, sticky="w", pady=(4, 14))

    ttk.Label(frame, text="Port HTTP").grid(row=2, column=0, sticky="w", pady=(0, 8))
    ttk.Entry(frame, textvariable=port_var, width=14).grid(
        row=2, column=1, sticky="w", pady=(0, 8)
    )
    ttk.Label(frame, text="Host: 0.0.0.0").grid(
        row=2, column=2, sticky="w", padx=(12, 0), pady=(0, 8)
    )

    ttk.Label(frame, text="Folder manuscrise").grid(
        row=3, column=0, sticky="w", pady=(0, 8)
    )
    ttk.Entry(frame, textvariable=folder_var, width=54).grid(
        row=3, column=1, sticky="ew", pady=(0, 8)
    )
    ttk.Button(frame, text="Alege...", command=browse_root).grid(
        row=3, column=2, sticky="w", padx=(12, 0), pady=(0, 8)
    )

    ttk.Label(frame, text="Fisier metadate").grid(
        row=4, column=0, sticky="w", pady=(0, 8)
    )
    ttk.Entry(frame, textvariable=metadata_var, width=54).grid(
        row=4, column=1, sticky="ew", pady=(0, 8)
    )
    ttk.Button(frame, text="Alege...", command=browse_metadata).grid(
        row=4, column=2, sticky="w", padx=(12, 0), pady=(0, 8)
    )

    ttk.Label(
        frame,
        text="Lasati fisierul de metadate gol pentru varianta implicita din proiect.",
        wraplength=520,
        justify="left",
    ).grid(row=5, column=0, columnspan=3, sticky="w", pady=(0, 8))

    ttk.Checkbutton(
        frame,
        text="Configureaza automat portul in Windows Firewall pentru acces extern",
        variable=configure_firewall_var,
    ).grid(row=6, column=0, columnspan=3, sticky="w", pady=(0, 6))

    ttk.Checkbutton(
        frame,
        text="Porneste pe HTTPS",
        variable=use_https_var,
    ).grid(row=7, column=0, columnspan=3, sticky="w", pady=(0, 8))

    ttk.Label(frame, text="Certificat HTTPS").grid(
        row=8, column=0, sticky="w", pady=(0, 8)
    )
    ttk.Entry(frame, textvariable=certfile_var, width=54).grid(
        row=8, column=1, sticky="ew", pady=(0, 8)
    )
    ttk.Button(frame, text="Alege...", command=browse_certfile).grid(
        row=8, column=2, sticky="w", padx=(12, 0), pady=(0, 8)
    )

    ttk.Label(frame, text="Cheie privata HTTPS").grid(
        row=9, column=0, sticky="w", pady=(0, 8)
    )
    ttk.Entry(frame, textvariable=keyfile_var, width=54).grid(
        row=9, column=1, sticky="ew", pady=(0, 8)
    )
    ttk.Button(frame, text="Alege...", command=browse_keyfile).grid(
        row=9, column=2, sticky="w", padx=(12, 0), pady=(0, 8)
    )

    ttk.Label(
        frame,
        text="Pentru HTTPS foloseste un certificat PEM/CRT. Cheia privata este optionala daca se afla deja in acelasi fisier.",
        wraplength=520,
        justify="left",
    ).grid(row=10, column=0, columnspan=3, sticky="w", pady=(0, 8))

    ttk.Checkbutton(
        frame,
        text="Deschide automat browserul dupa pornire",
        variable=open_browser_var,
    ).grid(row=11, column=0, columnspan=3, sticky="w", pady=(0, 16))

    actions = ttk.Frame(frame)
    actions.grid(row=12, column=0, columnspan=3, sticky="e")
    ttk.Button(actions, text="Renunta", command=cancel).grid(
        row=0, column=0, padx=(0, 8)
    )
    ttk.Button(actions, text="Porneste serverul", command=submit).grid(
        row=0, column=1
    )

    root_window.bind("<Return>", lambda _event: submit())
    root_window.protocol("WM_DELETE_WINDOW", cancel)
    root_window.mainloop()
    return selection["value"]


def show_error_dialog(title: str, message: str) -> None:
    try:
        import tkinter as tk
        from tkinter import messagebox

        root_window = tk.Tk()
        root_window.withdraw()
        messagebox.showerror(title, message, parent=root_window)
        root_window.destroy()
    except Exception:
        pass
    print(message)


def open_browser_later(url: str) -> None:
    timer = threading.Timer(1.0, lambda: webbrowser.open(url))
    timer.daemon = True
    timer.start()


def main() -> int:
    args = parse_args()
    try:
        initial = build_initial_settings(args)
    except ValueError as exc:
        print(str(exc))
        return 1

    try:
        selection = prompt_settings(initial)
    except ImportError:
        print(
            "Interfata grafica Tk nu este disponibila in aceasta instalare Python."
        )
        return 1

    if selection is None:
        return 0

    metadata_path = (
        Path(selection.metadata_path).expanduser()
        if selection.metadata_path.strip()
        else None
    )
    certfile = (
        Path(selection.certfile).expanduser()
        if selection.use_https and selection.certfile.strip()
        else None
    )
    keyfile = (
        Path(selection.keyfile).expanduser()
        if selection.use_https and selection.keyfile.strip()
        else None
    )
    try:
        app_server = server.build_server(
            HOST,
            selection.port,
            Path(selection.root),
            metadata_path,
            certfile,
            keyfile,
        )
    except (ValueError, OSError) as exc:
        show_error_dialog("Pornirea serverului a esuat", str(exc))
        return 1

    save_launcher_settings(selection)
    host, port = app_server.server_address
    scheme = app_server.url_scheme
    local_url = server.build_local_url(scheme, port)
    print(f"Server pornit pe {server.build_bound_url(scheme, host, port)}")
    print(f"Deschidere locala: {local_url}")
    print(f"Indexare din: {app_server.index.root}")
    print(f"Metadate salvate in: {app_server.index.metadata_path}")
    if app_server.certfile is not None:
        print(f"HTTPS activ cu certificat: {app_server.certfile}")
        if app_server.keyfile is not None:
            print(f"Cheie privata folosita: {app_server.keyfile}")
    if server.should_configure_firewall(HOST, selection.configure_firewall):
        firewall_ok, firewall_message = server.ensure_windows_firewall_rule(port)
        print(firewall_message)
    if selection.open_browser:
        open_browser_later(local_url)

    try:
        app_server.serve_forever()
    except KeyboardInterrupt:
        print("\nServer oprit.")
    finally:
        app_server.server_close()
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
