"""The notification digest: one mail per person for what arrived since the last run.

    python -m app.cli notify digest [--check]

Registered from `cli_accounts.register` (one line there). Meant for host cron every thirty
minutes (`docs/RUNBOOK.md` Mail); the window is whatever has accrued, not a fixed span, so a
missed run catches up. Digest, not firehose: a person who was mentioned five times gets one
message saying five things are waiting, each with its deep link. The mail lists what the side
panel's Needs-you tab lists, nothing else (T14): a mention or a turn, unread and unresolved; a
reply, a resolution or a decision is news for the bell and is stamped delivered without a
mail, like a row already read in the app. A person who turned mail off (`account_preferences`), has
no email, or is not active is skipped, their rows left for a later decision. The `notify` cap
(40 a day per person, `services/mail.py`) leaves the rows undelivered for the next run rather
than dropping them. With no provider (`MAIL_PROVIDER=none`) the command says so and stamps
nothing, so it is inert until the app-only env file carries the mail lines. `--check` prints
what would be sent and writes nothing.
"""

from __future__ import annotations

import argparse
from datetime import UTC, datetime

from sqlalchemy import select

from app import db as appdb
from app.config import settings
from app.models import Account
from app.models.discussion import AccountPreference, Notification
from app.services import mail
from app.services.discussion import NEEDS_YOU

SITE = "Duty Free Professor"


def _batches(db) -> list[dict]:
    """Undelivered rows grouped by recipient, with what the mail would say."""
    rows = list(db.scalars(select(Notification).where(Notification.delivered_at.is_(None))
                           .order_by(Notification.recipient_id, Notification.created_at, Notification.id)))
    if not rows:
        return []
    ids = {r.recipient_id for r in rows} | {r.actor_id for r in rows if r.actor_id}
    people = {a.id: a for a in db.scalars(select(Account).where(Account.id.in_(ids)))}
    prefs = {p.account_id: p.mail_notifications for p in db.scalars(
        select(AccountPreference).where(AccountPreference.account_id.in_(ids)))}
    out: list[dict] = []
    for r in rows:
        if not out or out[-1]["account_id"] != r.recipient_id:
            person = people.get(r.recipient_id)
            out.append({"account_id": r.recipient_id, "person": person, "rows": [],
                        "wants_mail": prefs.get(r.recipient_id, True),
                        "skip": (None if person and person.status == "active" and person.email and prefs.get(r.recipient_id, True)
                                 else "no email" if not (person and person.email) else "mail off" if not prefs.get(r.recipient_id, True) else person.status)})
        out[-1]["rows"].append(r)
    for b in out:
        # What the panel's Needs-you tab lists, nothing else: addressed to the person, unread, open.
        b["to_mail"] = [r for r in b["rows"] if not r.read and not r.resolved and r.kind in NEEDS_YOU]
        b["lines"] = [f"- {people.get(r.actor_id).display_name if r.actor_id in people else 'Someone'} "
                      f"{_verb(r.kind)} on {r.context_label}" + (f": {r.body}" if r.body else "") + f"\n  {r.url}"
                      for r in b["to_mail"]]
    return out


def _verb(kind: str) -> str:
    return {"mention": "mentioned you", "reply": "replied", "resolved": "resolved a conversation",
            "decision": "recorded a decision", "turn": "asked you", "status": "made a change"}.get(kind, "notified you")


def compose(person: Account, lines: list[str]) -> tuple[str, str]:
    n = len(lines)
    subject = f"{n} thing{'s' if n != 1 else ''} waiting for you on {SITE}"
    base = (settings.public_base_url or "").rstrip("/")
    text = (f"Hello {person.display_name},\n\nSince the last note:\n\n" + "\n".join(lines) +
            f"\n\nThe bell on the site has the same list. Turn these mails off under Account: {base}/account\n")
    return subject, text


def cmd_digest(args: argparse.Namespace) -> int:
    now = datetime.now(UTC)
    if not mail.configured():
        print("notify digest: no mail provider on this host; nothing sent, nothing stamped")
        return 0
    sent = stamped = skipped = 0
    with appdb.SessionLocal() as db:  # looked up at call time, so the test engine reaches it
        for b in _batches(db):
            person = b["person"]
            if b["skip"]:
                skipped += len(b["rows"])
                print(f"skip {person.username if person else b['account_id']}: {b['skip']} ({len(b['rows'])} row(s) left)")
                continue
            name = mail.bucket("notify", b["account_id"])
            if b["to_mail"]:
                if not mail.allowed(db, name, now=now):
                    print(f"cap  {person.username}: the notify cap is spent; {len(b['to_mail'])} row(s) wait for the next run")
                    skipped += len(b["rows"])
                    continue
                subject, text = compose(person, b["lines"])
                if args.check:
                    print(f"would send to {person.username}: {subject}\n" + "\n".join("  " + l for l in text.splitlines()))
                else:
                    mail.mailer().send(person.email, subject, text)
                    mail.record(db, name, now=now)
                    sent += 1
            for r in b["rows"]:
                if not args.check:
                    r.delivered_at = now
                stamped += 1
        if args.check:
            db.rollback()
            print(f"[check, nothing written] would send {sent} mail(s); {stamped} row(s) would be stamped; {skipped} skipped")
            return 0
        db.commit()
    print(f"notify digest: sent {sent} mail(s), stamped {stamped} row(s), skipped {skipped}")
    return 0


def register(sub: argparse._SubParsersAction) -> None:
    notify = sub.add_parser("notify", help="the notification digest mail (host cron, every thirty minutes)")
    nsub = notify.add_subparsers(dest="notify_command", required=True)
    digest = nsub.add_parser("digest", help="one mail per person for the notifications not yet delivered")
    digest.add_argument("--check", action="store_true", help="print what would be sent; write nothing")
    digest.set_defaults(func=cmd_digest)
