from __future__ import annotations

import json
import os
import re
import subprocess
import time
from pathlib import Path

PENDING_ACTIONS: dict[int, object] = {}
EVENT_NOTIFICATION_IDS: dict[int, int] = {}


def journal_send(**fields: str) -> None:
    args = ["systemd-cat", "-t", "agent-guard"]
    line = " ".join(f"{k}={v}" for k, v in fields.items())
    try:
        subprocess.run(args, input=line + "\n", text=True, timeout=2, check=False)
    except OSError:
        pass


GATE_PY = os.environ.get("NOTIF_GATE_PY", "/usr/local/lib/notif-gate/notif_gate.py")
EMITTER = os.path.realpath(__file__)


def gate_allows(summary: str, body: str) -> bool:
    """Default-deny: only an owner-approved source may reach the session bus.

    Anything other than a clean approval — gate absent, crashed, or slow — denies
    and leaves the caller otherwise untouched.
    """
    try:
        proc = subprocess.run(
            ["python3", GATE_PY, "check", EMITTER, summary, body, "dbus-gated"],
            capture_output=True, text=True, timeout=5, check=False,
        )
    except (OSError, subprocess.SubprocessError) as exc:
        journal_send(SM_TIER="notice", SM_SOURCE="agent-guard", SM_ACTION="notify_gate_unavailable", SM_TARGET=str(exc))
        return False
    return proc.returncode == 0


def gdbus_notify(summary: str, body: str, actions: list[str], urgency: str = "normal", replaces_id: int = 0) -> int:
    if not gate_allows(summary, body):
        return 0
    action_array = []
    for action in actions:
        key = action.lower().replace(" ", "_")
        action_array.extend([key, action])
    args = [
        "gdbus", "call", "--session",
        "--dest", "org.freedesktop.Notifications",
        "--object-path", "/org/freedesktop/Notifications",
        "--method", "org.freedesktop.Notifications.Notify",
        "agent-guard", str(replaces_id), "dialog-warning", summary, body,
        json.dumps(action_array).replace('"', "'"),
        "{'urgency': <byte 2>}" if urgency == "critical" else "{'urgency': <byte 1>}",
        "0",
    ]
    proc = subprocess.run(args, capture_output=True, text=True, check=False)
    match = re.search(r"\(uint32\s+(\d+),?\)", proc.stdout)
    return int(match.group(1)) if match else 0


def clean_tmp_safe(root: Path = Path("/tmp"), uid: int = 1000, older_than_seconds: int = 72 * 3600) -> int:
    cutoff = time.time() - older_than_seconds
    removed = 0
    root = root.resolve()
    for dirpath, dirnames, filenames in os.walk(root, topdown=False):
        base = Path(dirpath)
        try:
            if not base.resolve().is_relative_to(root):
                continue
        except OSError:
            continue
        for name in filenames:
            path = base / name
            try:
                st = path.lstat()
                if st.st_uid != uid or st.st_mtime > cutoff:
                    continue
                path.unlink()
                removed += 1
            except OSError:
                continue
        for name in dirnames:
            path = base / name
            try:
                st = path.lstat()
                if st.st_uid == uid and st.st_mtime <= cutoff:
                    path.rmdir()
            except OSError:
                continue
    journal_send(SM_TIER="notice", SM_SOURCE="agent-guard", SM_ACTION="clean_tmp", SM_TARGET=str(root), SM_REMOVED=str(removed))
    return removed


def notify_event(event) -> int:
    urgency = "critical" if event.tier == "critical" else "normal"
    event_key = id(event)
    replaces_id = EVENT_NOTIFICATION_IDS.get(event_key, 0)
    nid = gdbus_notify(
        f"system-monitor: {event.source}",
        event.reason,
        event.actions or ["Dismiss"],
        urgency,
        replaces_id=replaces_id,
    )
    if nid:
        if replaces_id:
            PENDING_ACTIONS.pop(replaces_id, None)
        PENDING_ACTIONS[nid] = event
        EVENT_NOTIFICATION_IDS[event_key] = nid
    journal_send(SM_TIER=event.tier, SM_SOURCE=event.source, SM_ACTION="notify", SM_TARGET=str(nid))
    return nid


def action_monitor(callback, stop) -> None:
    cmd = [
        "gdbus", "monitor", "--session",
        "--dest", "org.freedesktop.Notifications",
        "--object-path", "/org/freedesktop/Notifications",
    ]
    try:
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
    except OSError:
        return
    with proc:
        while not stop.is_set():
            line = proc.stdout.readline() if proc.stdout else ""
            if not line:
                if proc.poll() is not None:
                    return
                continue
            if "ActionInvoked" not in line:
                continue
            match = re.search(r"\((uint32 |)(\d+), '([^']+)'\)", line)
            if not match:
                continue
            nid = int(match.group(2))
            action = match.group(3)
            event = PENDING_ACTIONS.pop(nid, None)
            if event:
                callback(event, action)
