#!/usr/bin/python3
"""Run movie_recs and push the result to ntfy as a markdown notification.

Intended for the movie-recs.timer systemd unit. Failures are pushed too --
a recommendation job that silently stops producing is worse than a noisy one.
"""
import base64
import json
import os
import subprocess
import sys
import urllib.request

NTFY = "https://ntfy.rcjohnstone.com/movies"
# ntfy is auth-default-access: deny-all, so publishing needs the `bot`
# credential. Mirrored from the rbw entry `ntfy-bot`; rbw is the source of
# truth. Kept in a 0600 file because this runs unattended from a timer.
NTFY_ENV = os.path.expanduser("~/.config/ntfy/publish.env")
# Resolved next to this script, not via ~/.local/bin. That directory used to
# be a dotbot link into hosts/<host>/bin; it is now a real directory owned by
# the package managers (meta/base.conf.yaml), so the movie_recs link vanished
# and every run since died with FileNotFoundError. The sibling path is what
# the unit already uses for this script itself and cannot drift from it.
RECS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "movie_recs")
COUNT = "10"


def _auth_header():
    user, pw = os.environ.get("NTFY_USER"), os.environ.get("NTFY_PASS")
    if not (user and pw):
        try:
            with open(NTFY_ENV) as fh:
                vals = dict(
                    line.strip().split("=", 1)
                    for line in fh if "=" in line and not line.startswith("#"))
            user, pw = vals.get("NTFY_USER"), vals.get("NTFY_PASS")
        except OSError:
            return None
    if not (user and pw):
        return None
    token = base64.b64encode(f"{user}:{pw}".encode()).decode()
    return f"Basic {token}"


def push(title, body, tags, priority="default", markdown=True):
    headers = {
        "Title": title,
        "Tags": tags,
        "Priority": priority,
        "Content-Type": "text/plain; charset=utf-8",
    }
    auth = _auth_header()
    if auth:
        headers["Authorization"] = auth
    if markdown:
        headers["Markdown"] = "yes"
    req = urllib.request.Request(
        NTFY, data=body.encode("utf-8"), headers=headers, method="POST")
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.status


def main():
    try:
        proc = subprocess.run(
            [RECS, "-n", COUNT, "--json"],
            capture_output=True, text=True, timeout=2400)
    except subprocess.TimeoutExpired:
        push("Movie recs failed", "movie_recs timed out after 40 minutes.",
             "warning", "high", markdown=False)
        return 1

    if proc.returncode != 0:
        tail = (proc.stderr or "no stderr").strip().splitlines()[-6:]
        push("Movie recs failed",
             "exit %d\n\n%s" % (proc.returncode, "\n".join(tail)),
             "warning", "high", markdown=False)
        return 1

    try:
        data = json.loads(proc.stdout)
    except json.JSONDecodeError:
        push("Movie recs failed", "could not parse output as JSON",
             "warning", "high", markdown=False)
        return 1

    recs = data.get("recommendations", [])
    if not recs:
        push("Movie recs: nothing today",
             "The run completed but every candidate was filtered out.",
             "warning", "default", markdown=False)
        return 0

    lines = []
    for r in recs:
        t = r["tmdb"]
        lines.append("**%s** (%s) · %s/10, %sk votes"
                     % (t["title"], t["year"], t["score"], t["votes"] // 1000))
        lines.append("%s" % r.get("reason", "").strip())
        lines.append("*from: %s*" % ", ".join(r.get("anchors", [])))
        lines.append("")

    dropped = (len(data.get("rejected_not_in_tmdb", []))
               + len(data.get("rejected_already_owned", []))
               + len(data.get("rejected_below_min_votes", []))
               + len(data.get("rejected_invented_cluster", [])))
    lines.append("_%d shown, %d candidates filtered_" % (len(recs), dropped))

    push("%d film recommendations" % len(recs), "\n".join(lines), "clapper")
    return 0


if __name__ == "__main__":
    sys.exit(main())
