"""The digest: one email per person, once things have been waiting.

The bell is where notifications live; email is for the person who is not
looking at it. The rule (rian, 2026-09-07, after review): when someone has
new items in their bell, wait until nothing new has arrived for a quiet
while (an hour, `EASEL_DIGEST_QUIET_MINUTES`), then send ONE email saying
there are new notifications in their Easel. Items they read in the meantime
do not count; items already mailed are not mailed again (`delivered_at`, the
standard's §7 seam, stamped per row).

The digest is sent through the outbox like every other message, so Dev Mode
holds it and the record shows what would have gone. A held digest still
stamps its rows: the decision was made, the outbox says why it did not go.
"""

import logging
from datetime import datetime, timedelta, timezone

from sqlalchemy import select
from sqlalchemy.orm import Session

from app import bw_config
from app.config import get_settings
from app.models import Notification
from app.services import outbound

log = logging.getLogger(__name__)

SUBJECT = "New notifications in your Design Easel"
MAX_LINES = 12

KIND_SENTENCE = {
    "mention": "mentioned you",
    "reply": "replied in a thread",
    "comment": "commented",
    "turn": "needs you",
    "status": "made a change",
    "resolved": "resolved a thread",
    "decision": "decided something",
}


def _aware(dt: datetime) -> datetime:
    return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)


def _line(n: Notification) -> str:
    what = KIND_SENTENCE.get(n.kind, "notified you")
    where = f" on {n.context_label}" if n.context_label else ""
    said = f": {n.body}" if n.body else ""
    return f"- {n.actor} {what}{where}{said}"


def run_once(db: Session, *, quiet_minutes: int | None = None,
             now: datetime | None = None) -> int:
    """Mail everyone whose unread, unmailed items have gone quiet. Returns how
    many digests were queued (sent or held)."""
    quiet = get_settings().digest_quiet_minutes if quiet_minutes is None else quiet_minutes
    now = now or datetime.now(timezone.utc)
    cutoff = now - timedelta(minutes=quiet)
    waiting = db.scalars(select(Notification).where(
        Notification.read_at.is_(None),
        Notification.delivered_at.is_(None)).order_by(Notification.occurred_at)).all()
    by_user: dict[str, list[Notification]] = {}
    for n in waiting:
        by_user.setdefault(n.recipient, []).append(n)

    queued = 0
    base = f"https://{bw_config.HOST}"
    for user, items in by_user.items():
        if max(_aware(n.occurred_at) for n in items) > cutoff:
            continue  # still arriving; wait for the quiet
        count = len(items)
        lines = [_line(n) for n in items[:MAX_LINES]]
        if count > MAX_LINES:
            lines.append(f"- and {count - MAX_LINES} more")
        body = (f"{'One thing is' if count == 1 else f'{count} things are'} waiting for "
                f"you in Design Easel:\n\n" + "\n".join(lines)
                + "\n\nOpen the bell to see them all.")
        projects = {n.project_id for n in items if n.project_id}
        url = f"{base}/p/{next(iter(projects))}" if len(projects) == 1 else f"{base}/"
        outbound.queue(db, recipient=user, kind="digest", subject=SUBJECT,
                       body=body, url=url,
                       project_id=next(iter(projects)) if len(projects) == 1 else None)
        for n in items:
            n.delivered_at = now
        queued += 1
    db.flush()
    if queued:
        log.info("digest: %d queued", queued)
    return queued
