"""Admin -> user notifications.

The admin writes a notification in /admintools -> Notifications; it is either
sent to everyone or to one specific user, and shows up under the bell icon in
the app. Read state is tracked per user so the bell can show an unread count.

Plain JSON file storage (CONFIG_DIR/notifications.json), same pattern as the
other stores. Shape:
    {"items": [{id, title, message, kind, target, created_at}, ...],   # newest first
     "reads": {"<username>": ["<notification id>", ...]}}
"""
from __future__ import annotations

import json
import threading
import uuid
from datetime import datetime, timezone

import app_config

NOTIF_FILE = app_config.CONFIG_DIR / "notifications.json"
MAX_ITEMS = 100            # oldest notifications are dropped beyond this
KINDS = ("info", "success", "warning")
_lock = threading.Lock()


def _now() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


def _load() -> dict:
    if not NOTIF_FILE.exists():
        return {"items": [], "reads": {}}
    try:
        data = json.loads(NOTIF_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {"items": [], "reads": {}}
    if not isinstance(data.get("items"), list):
        data["items"] = []
    if not isinstance(data.get("reads"), dict):
        data["reads"] = {}
    return data


def _save(data: dict) -> None:
    app_config.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    NOTIF_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")


def add(title: str, message: str, kind: str = "info", target: str = "all") -> dict:
    """target is "all" or a username."""
    item = {
        "id": uuid.uuid4().hex,
        "title": title,
        "message": message,
        "kind": kind if kind in KINDS else "info",
        "target": target or "all",
        "created_at": _now(),
    }
    with _lock:
        data = _load()
        data["items"].insert(0, item)
        dropped = data["items"][MAX_ITEMS:]
        data["items"] = data["items"][:MAX_ITEMS]
        if dropped:
            _forget_reads(data, {d["id"] for d in dropped})
        _save(data)
    return item


def _forget_reads(data: dict, ids: set) -> None:
    for name, read in list(data["reads"].items()):
        data["reads"][name] = [i for i in read if i not in ids]


def delete(notif_id: str) -> bool:
    with _lock:
        data = _load()
        before = len(data["items"])
        data["items"] = [i for i in data["items"] if i.get("id") != notif_id]
        if len(data["items"]) == before:
            return False
        _forget_reads(data, {notif_id})
        _save(data)
        return True


def list_all() -> list[dict]:
    """Admin view: every notification plus how many users have read it."""
    data = _load()
    counts: dict[str, int] = {}
    for read in data["reads"].values():
        for nid in read:
            counts[nid] = counts.get(nid, 0) + 1
    return [dict(i, read_count=counts.get(i["id"], 0)) for i in data["items"]]


def for_user(username: str) -> list[dict]:
    """What one user sees: broadcasts + notifications addressed to them, with a `read` flag."""
    data = _load()
    read = set(data["reads"].get(username, []))
    return [
        {
            "id": i["id"], "title": i["title"], "message": i["message"],
            "kind": i.get("kind", "info"), "created_at": i["created_at"],
            "read": i["id"] in read,
        }
        for i in data["items"]
        if i.get("target", "all") in ("all", username)
    ]


def mark_read(username: str, ids: list | None = None) -> None:
    """ids=None marks everything currently visible to the user as read."""
    with _lock:
        data = _load()
        visible = {i["id"] for i in data["items"] if i.get("target", "all") in ("all", username)}
        wanted = visible if ids is None else (visible & set(ids))
        read = set(data["reads"].get(username, []))
        data["reads"][username] = sorted(read | wanted)
        _save(data)


def forget_user(username: str) -> None:
    """Called when a user is deleted so their read-state and private notifications go too."""
    with _lock:
        data = _load()
        data["reads"].pop(username, None)
        data["items"] = [i for i in data["items"] if i.get("target") != username]
        _save(data)
