from __future__ import annotations

import time
import tkinter as tk
from concurrent.futures import Future, ThreadPoolExecutor
from datetime import datetime
from tkinter import messagebox, ttk

from gui.about import AboutPage
from gui.adapters import (CONFIG_PATH, DATABASE_PATH, ConfigurationAdapter, DataAdapter,
                          DriverStatusAdapter, RelayAdapter, RemediationReadAdapter, health_from_status)
from gui.alerts import AlertsPage
from gui.configuration import ConfigurationPage
from gui.events import EventsPage
from gui.logs import LogsPage
from gui.overview import OverviewPage
from gui.processes import ProcessesPage
from gui.remediation import RemediationPage
from gui.services import ServicesPage
from gui.theme import COLORS, apply_theme
from gui.widgets import ReadOnlyDetailsDialog


class ProcessMonitorApp(tk.Tk):
    REFRESH_MS = 2000

    def __init__(self):
        super().__init__()
        self.title("ProcessMonitor Defensive Console")
        self.geometry("1480x900")
        self.minsize(1080, 680)
        self.resizable(True, True)
        try: self.tk.call("tk", "scaling", max(1.0, self.winfo_fpixels("1i") / 96.0))
        except tk.TclError: pass
        apply_theme(self)
        self.executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="processmonitor-gui")
        self.pending: Future | None = None
        self.started = time.monotonic()
        self.relay = RelayAdapter()
        self.driver = DriverStatusAdapter()
        self.data = DataAdapter()
        self.config_adapter = ConfigurationAdapter()
        self.remediation = RemediationReadAdapter()
        self.pages = {}
        self.nav_buttons = {}
        self._build_shell()
        self.protocol("WM_DELETE_WINDOW", self._close)
        self.after(50, self.request_refresh)

    def _build_shell(self):
        self.columnconfigure(1, weight=1); self.rowconfigure(1, weight=1)
        sidebar = ttk.Frame(self, style="Sidebar.TFrame", width=210); sidebar.grid(row=0, column=0, rowspan=2, sticky="nsew"); sidebar.grid_propagate(False)
        ttk.Label(sidebar, text="PROCESSMONITOR", background=COLORS["sidebar"], foreground=COLORS["text"],
                  font=("Bahnschrift SemiCondensed", 17)).pack(anchor="w", padx=16, pady=(20, 2))
        ttk.Label(sidebar, text="DEFENSIVE SECURITY TOOLKIT", background=COLORS["sidebar"], foreground=COLORS["bad"],
                  font=("Segoe UI Semibold", 8)).pack(anchor="w", padx=18, pady=(0, 22))
        top = ttk.Frame(self, style="Panel.TFrame", padding=(18, 11)); top.grid(row=0, column=1, sticky="ew")
        top.columnconfigure(8, weight=1)
        self.top_values = {}
        for index, label in enumerate(("Monitoring", "Driver", "Relay", "PID", "Timestamp")):
            block = ttk.Frame(top, style="Panel.TFrame"); block.grid(row=0, column=index, sticky="w", padx=(0, 28))
            ttk.Label(block, text=label.upper(), style="PanelMuted.TLabel", font=("Segoe UI Semibold", 8)).pack(anchor="w")
            value = ttk.Label(block, text="Checking...", style="Panel.TLabel", font=("Segoe UI Semibold", 10)); value.pack(anchor="w"); self.top_values[label] = value
        self.content = ttk.Frame(self); self.content.grid(row=1, column=1, sticky="nsew"); self.content.rowconfigure(0, weight=1); self.content.columnconfigure(0, weight=1)
        specs = [("Overview", lambda p: OverviewPage(p, self.service_action)), ("Live Processes", lambda p: ProcessesPage(p, self.show_process_details)), ("Events", EventsPage), ("Alerts", lambda p: AlertsPage(p, self.show_alert_details)),
                 ("Driver & Relay", lambda p: ServicesPage(p, self.service_action)), ("Remediation", RemediationPage),
                 ("Configuration", lambda p: ConfigurationPage(p, self.config_adapter)), ("Logs", LogsPage), ("About", AboutPage)]
        icons = {"Overview": "[+]", "Live Processes": "[P]", "Events": "[E]", "Alerts": "[!]",
                 "Driver & Relay": "[D]", "Remediation": "[R]", "Configuration": "[C]", "Logs": "[L]", "About": "[i]"}
        for name, constructor in specs:
            page = constructor(self.content); page.grid(row=0, column=0, sticky="nsew"); self.pages[name] = page
            button = ttk.Button(sidebar, text=f"{icons[name]}   {name}", style="Nav.TButton", command=lambda n=name: self.show_page(n))
            button.pack(fill="x"); self.nav_buttons[name] = button
        ttk.Label(sidebar, text="Authorized defensive use only", background=COLORS["sidebar"], foreground=COLORS["muted"], wraplength=170).pack(side="bottom", padx=18, pady=18)
        self.show_page("Overview")

    def show_page(self, name):
        self.pages[name].tkraise()
        for page_name, button in self.nav_buttons.items():
            button.configure(style="SelectedNav.TButton" if page_name == name else "Nav.TButton")

    def _load_snapshot(self):
        relay = self.relay.status(); driver = self.driver.status()
        try: config = self.config_adapter.load()
        except Exception as exc: config = {"_error": str(exc), "AllowList": [], "WatchList": []}
        jsonl_path = self.data.jsonl_path()
        return {"relay": relay, "driver": driver, "metrics": self.data.metrics(), "events": self.data.recent_events(),
                "alerts": self.data.alerts(), "processes": self.data.active_processes(), "jsonl_events": self.data.jsonl_events(),
                "threat": self.data.threat_score(),
                "jsonl_ok": jsonl_path.is_file(), "database_ok": DATABASE_PATH.is_file(), "database_path": str(DATABASE_PATH),
                "config": config, "remediation": self.remediation.snapshot(),
                "uptime": self._format_uptime(time.monotonic() - self.started)}

    @staticmethod
    def _format_uptime(seconds):
        hours, remainder = divmod(int(seconds), 3600); minutes, secs = divmod(remainder, 60)
        return f"{hours:02d}:{minutes:02d}:{secs:02d}"

    def request_refresh(self):
        if self.pending is None or self.pending.done():
            self.pending = self.executor.submit(self._load_snapshot)
            self._watch_future(self.pending, self._apply_snapshot)
        self.after(self.REFRESH_MS, self.request_refresh)

    def _watch_future(self, future, callback):
        if not future.done(): self.after(50, self._watch_future, future, callback); return
        try: callback(future.result())
        except Exception as exc:
            self.top_values["Monitoring"].configure(text="Error", foreground=COLORS["critical"])
            self.top_values["Timestamp"].configure(text=f"Read error: {str(exc)[:40]}")

    def _apply_snapshot(self, snapshot):
        driver, relay = snapshot["driver"], snapshot["relay"]
        health = health_from_status(driver, relay, snapshot["jsonl_ok"], snapshot["database_ok"])
        active = driver.get("running") and relay.get("running")
        self.top_values["Monitoring"].configure(text="Active" if active else health,
                                                 foreground=COLORS["good"] if active else COLORS["warn"] if health == "Degraded" else COLORS["critical"])
        self.top_values["Driver"].configure(text=driver["state"], foreground=COLORS["good"] if driver.get("running") else COLORS["muted"])
        self.top_values["Relay"].configure(text="Running" if relay["running"] else "Stopped", foreground=COLORS["good"] if relay["running"] else COLORS["muted"])
        self.top_values["PID"].configure(text=relay.get("pid") or "N/A")
        self.top_values["Timestamp"].configure(text=datetime.now().strftime("%b %d, %Y %I:%M:%S %p"))
        for page in self.pages.values():
            try: page.refresh(snapshot)
            except Exception: pass

    def service_action(self, action):
        if action == "refresh": self.request_refresh(); return
        if self.pending is not None and not self.pending.done():
            messagebox.showinfo("Please wait", "A monitoring refresh is already in progress."); return
        operation = self.relay.start if action == "start" else self.relay.stop
        self.pending = self.executor.submit(operation)
        def finished(_):
            messagebox.showinfo("Relay", f"Relay {action} completed."); self.request_refresh()
        self._watch_future(self.pending, finished)

    def show_process_details(self, process):
        if self.pending is not None and not self.pending.done():
            messagebox.showinfo("Please wait", "A monitoring refresh is already in progress.")
            return
        self.pending = self.executor.submit(self.data.process_details, process)

        def finished(details):
            value = lambda key: details.get(key) if details.get(key) not in (None, "") else "N/A"
            rules = self._format_findings(details.get("matched_rules") or [])
            lines = [
                f"PID: {value('pid')}", f"Name: {value('process_name')}", f"Path: {value('path')}",
                f"Parent PID: {value('parent_pid')}", f"Parent name: {value('parent_process_name')}",
                f"Command line: {value('command_line')}", f"Signer: {value('signer')}",
                f"Signature status: {value('signature_status')}", f"SHA-256: {value('sha256')}",
                f"Classification: {value('classification')}", f"Reason: {value('classification_reason')}",
                f"Matched rules:\n{rules if rules else 'No detection rules matched.'}", f"Risk score: {value('risk_score')}",
                f"First seen: {value('first_seen')}", f"Last seen: {value('last_seen')}",
            ]
            ReadOnlyDetailsDialog(self, "Process details", "\n".join(lines))

        self._watch_future(self.pending, finished)

    @staticmethod
    def _format_findings(findings):
        blocks = []
        for finding in findings:
            if not isinstance(finding, dict):
                blocks.append(str(finding)); continue
            evidence = finding.get("evidence") or {}
            evidence_text = ", ".join(f"{key}: {value}" for key, value in evidence.items()) or "No additional evidence"
            blocks.append(f"- {finding.get('rule_id', 'Unknown')} [{finding.get('category', 'unknown')}, {finding.get('severity', 'info')}]\n  {finding.get('reason', 'Rule matched')}\n  Evidence: {evidence_text}")
        return "\n".join(blocks)

    def show_alert_details(self, alert):
        details = self.data.alert_details(alert)
        value = lambda key: details.get(key) if details.get(key) not in (None, "") else "N/A"
        findings = self._format_findings(details.get("matched_rules") or [])
        lines = [f"Process: {value('process_name')}", f"PID: {value('pid')}",
                 f"Timestamp: {value('timestamp')}", f"Classification: {value('classification')}",
                 f"Risk score: {value('risk_score')}", f"Command line: {value('command_line')}",
                 f"Parent process: {value('parent_process_name')}",
                 f"Matched rules:\n{findings if findings else 'No detection rules matched.'}"]
        ReadOnlyDetailsDialog(self, "Alert details", "\n".join(lines))

    def _close(self):
        self.executor.shutdown(wait=False, cancel_futures=True)
        self.destroy()


def main() -> int:
    app = ProcessMonitorApp()
    app.mainloop()
    return 0


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