"""TRMNL e-ink screen — a session dashboard on the wall.

The archive's TRMNL screens were POLLED: TRMNL's cloud fetched a public
endpoint through a tunnel. This app is localhost-only by design, so the
direction flips — we PUSH to a TRMNL private-plugin webhook on a timer.
No public endpoint, no tunnel, nothing to expose.

Setup for the user: TRMNL → Plugins → Private Plugin → strategy "Webhook",
copy the plugin UUID into settings here, paste the markup below into the
plugin's template. We then post {"merge_variables": {...}} every few
minutes (TRMNL rate-limits to ~12 pushes/hour, so 5 minutes is the floor).
"""
import json
import os
import threading
import time
import urllib.request

from fastapi import APIRouter, HTTPException, Request

from clipshelf import net, config, db, obs_client

router = APIRouter(tags=["trmnl"])

# overridable for self-hosted TRMNL (BYOS) servers
API = os.getenv("TRMNL_API", "https://usetrmnl.com/api/custom_plugins/")
MIN_INTERVAL_S = 300


def _cfg() -> dict:
    c = config.CFG.get("trmnl") or {}
    return {"uuid": str(c.get("uuid") or ""),
            "interval_s": max(MIN_INTERVAL_S, int(c.get("interval_s") or 600)),
            "enabled": bool(c.get("enabled"))}


def enabled() -> bool:
    c = _cfg()
    return bool(c["enabled"] and c["uuid"])


def _hms(sec) -> str:
    sec = int(max(0, sec or 0))
    h, m = divmod(sec // 60, 60)
    return f"{h}h {m:02d}m" if h else f"{m}m"


def _ago(epoch) -> str:
    d = max(0, time.time() - (epoch or 0))
    if d < 3600:
        return f"{int(d // 60)}m ago"
    if d < 86400:
        return f"{int(d // 3600)}h ago"
    return f"{int(d // 86400)}d ago"


def build() -> dict:
    """What the screen renders. This is a shelf, not a status light: what
    is in the archive, what came out of last night, what was said. The
    health bits are one small line at the bottom — a wall display should be
    worth glancing at when nothing is wrong."""
    from clipshelf import game_detect, problems, vod_assets
    from clipshelf.workers import transcribe_worker

    now = time.time()
    recording = False
    rec_ms = 0
    try:
        if obs_client.connected():
            rec_ms = obs_client.get_obs_timecode()
            recording = rec_ms > 0
    except Exception:
        pass

    conn = db.connect()
    try:
        vod = conn.execute(
            "SELECT id, filename, start_epoch, duration_ms FROM vods"
            " ORDER BY start_epoch DESC LIMIT 1").fetchone()
        marks_today = conn.execute(
            "SELECT COUNT(*) n FROM gsi_events WHERE event_type='bookmark'"
            " AND epoch_ms > ?", (int((now - 18 * 3600) * 1000),)).fetchone()["n"]
        pending = conn.execute(
            "SELECT COUNT(*) n FROM pending_cuts WHERE status='pending'").fetchone()["n"]
        try:
            tray = conn.execute(
                "SELECT COUNT(*) n FROM ingest_clips WHERE status='new'").fetchone()["n"]
        except Exception:
            tray = 0        # one missing count must not cost the whole screen
        vods_total, ms_total = conn.execute(
            "SELECT COUNT(*) n, COALESCE(SUM(duration_ms), 0) ms FROM vods").fetchone()

        last = {"date": "", "dur": "", "kills": 0, "events": 0, "games": "",
                "marks": 0, "chapters": []}
        if vod:
            counts = {r["event_type"]: r["n"] for r in conn.execute(
                """SELECT event_type, COUNT(*) n FROM gsi_events
                   WHERE vod_id = ? GROUP BY event_type""", (vod["id"],))}
            chaps = [dict(r) for r in conn.execute(
                """SELECT game_tag, start_time_ms FROM vod_chapters
                   WHERE vod_id = ? AND start_time_ms IS NOT NULL
                   ORDER BY start_time_ms""", (vod["id"],))]
            seen, names = set(), []
            for c in chaps:
                nm = game_detect.display_name(c["game_tag"])
                if nm not in seen:
                    seen.add(nm)
                    names.append(nm)
            last = {
                "date": time.strftime("%a %d %b", time.localtime(vod["start_epoch"])),
                "dur": _hms(vod["duration_ms"] / 1000),
                "kills": counts.get("kill", 0),
                "events": sum(counts.values()),
                "marks": counts.get("bookmark", 0),
                "games": " · ".join(names[:4]) or "—",
                "chapters": [
                    {"game": game_detect.display_name(c["game_tag"]),
                     "at": _hms(c["start_time_ms"] / 1000)} for c in chaps[:5]],
            }

        tdone, tsegs = conn.execute(
            """SELECT COUNT(*) n, COALESCE(SUM(segments), 0) s FROM transcripts
               WHERE status='done'""").fetchone()

        # a couple of lines from the archive, picked at random — the point of
        # transcribing everything is that there is something in there
        quotes = []
        try:
            for r in conn.execute(
                    """SELECT f.text, f.t_ms, v.start_epoch FROM transcript_fts f
                       JOIN vods v ON v.id = f.vod_id
                       WHERE LENGTH(f.text) BETWEEN 24 AND 120
                       ORDER BY RANDOM() LIMIT 2"""):
                quotes.append({
                    "text": " ".join(str(r["text"]).split()),
                    "when": time.strftime("%a %d %b", time.localtime(r["start_epoch"])),
                    "at": _hms((r["t_ms"] or 0) / 1000),
                })
        except Exception:
            pass
    finally:
        conn.close()

    # the clips shelf: what actually came out of all that recording. One
    # walk, newest four shown and the rest counted.
    clips, clips_total = [], 0
    try:
        from clipshelf import clips_api
        saved = clips_api.clips_saved(limit=1000)["clips"]
        clips_total = len(saved)
        clips = [{"name": c["name"][:34],
                  "game": c.get("game_label") or c.get("game") or "",
                  "ago": _ago(c["ts"]),
                  "shared": bool(c.get("share"))} for c in saved[:4]]
    except Exception:
        pass

    game = game_detect.current["game"]
    return {
        "app": config.APP_NAME,
        "recording": recording,
        "rec_for": _hms(rec_ms / 1000) if recording else "",
        "game": game_detect.display_name(game) if game else "",
        "playing": bool(game),
        "marks_today": marks_today,
        "pending_cuts": pending,
        "tray": tray,
        "library": {
            "vods": vods_total,
            "hours": f"{ms_total / 3600000:.0f}h",
            "clips": clips_total,
            "transcribed": tdone,
            "lines": tsegs,
        },
        "last": last,
        "clips": clips,
        "quotes": quotes,
        "problems": len(problems.items()),
        "obs": obs_client.connected(),
        "whisper": transcribe_worker.available(),
        # the screen keeps showing the last push forever if this machine goes
        # away, so every payload says plainly when it was made
        "updated": time.strftime("%H:%M"),
        "updated_full": time.strftime("%a %d %b · %H:%M"),
        "pushed_at": int(now),
    }


def push() -> dict:
    c = _cfg()
    if not c["uuid"]:
        raise HTTPException(400, "no TRMNL plugin UUID configured")
    payload = json.dumps({"merge_variables": build()}).encode()
    req = urllib.request.Request(API + c["uuid"], data=payload,
                                 headers={"Content-Type": "application/json"},
                                 method="POST")
    try:
        with urllib.request.urlopen(req, timeout=30, context=net.context()) as r:
            return {"status": r.status, "sent": True}
    except Exception as e:
        from clipshelf import problems
        problems.record("TRMNL", e, "check the plugin UUID in settings")
        raise HTTPException(502, f"TRMNL push failed: {e}")


def _loop():
    time.sleep(90)
    last = 0.0
    while True:
        try:
            c = _cfg()
            if c["enabled"] and c["uuid"] and time.time() - last >= c["interval_s"]:
                push()
                last = time.time()
        except Exception:
            last = time.time()      # back off; the failure is already logged
        time.sleep(30)


def start():
    threading.Thread(target=_loop, daemon=True).start()


# The Liquid markup the user pastes into their private plugin. Shipped from
# the app so the screen and the payload can never drift apart.
TEMPLATE = """<div class="view view--full">
  <div class="layout">

    <div class="columns">
      <div class="column">
        <span class="label label--underline">last recording</span>
        <div class="markdown gap--small">
          <span class="title">{{ last.date }}</span>
          <span class="description">{{ last.dur }} · {{ last.games }}</span>
        </div>
      </div>
      <div class="column">
        <div class="grid grid--cols-2">
          <div class="item"><span class="value">{{ library.vods }}</span>
            <span class="label">recordings</span></div>
          <div class="item"><span class="value">{{ library.hours }}</span>
            <span class="label">archived</span></div>
          <div class="item"><span class="value">{{ library.clips }}</span>
            <span class="label">clips</span></div>
          <div class="item"><span class="value">{{ library.transcribed }}</span>
            <span class="label">transcribed</span></div>
        </div>
      </div>
    </div>

    <div class="columns">
      <div class="column">
        <span class="label label--underline">newest clips</span>
        {% for c in clips %}
        <div class="item">
          <div class="content">
            <span class="title title--small">{{ c.name }}</span>
            <span class="description">{{ c.game }} · {{ c.ago }}{% if c.shared %} · shared{% endif %}</span>
          </div>
        </div>
        {% else %}
        <span class="description">nothing cut yet</span>
        {% endfor %}
      </div>
      <div class="column">
        <span class="label label--underline">overheard</span>
        {% for q in quotes %}
        <div class="markdown gap--small">
          <span class="description">&ldquo;{{ q.text }}&rdquo;</span>
          <span class="label label--small">{{ q.when }} · {{ q.at }}</span>
        </div>
        {% else %}
        <span class="description">transcripts will show up here</span>
        {% endfor %}
      </div>
    </div>

  </div>

  <div class="title_bar">
    <span class="title">
      {% if recording %}&#9679; recording {{ rec_for }}{% if playing %} · {{ game }}{% endif %}
      {% elsif playing %}{{ game }}
      {% else %}{{ app }}{% endif %}
    </span>
    <span class="instance">
      {% if tray > 0 %}{{ tray }} to trim · {% endif %}
      {% if pending_cuts > 0 %}{{ pending_cuts }} queued · {% endif %}
      {% if problems > 0 %}{{ problems }} problems · {% endif %}
      {{ updated_full }}
    </span>
  </div>
</div>"""


@router.get("/api/trmnl")
def trmnl_state():
    c = _cfg()
    return {"enabled": c["enabled"], "has_uuid": bool(c["uuid"]),
            "interval_s": c["interval_s"], "min_interval_s": MIN_INTERVAL_S,
            "preview": build(), "template": TEMPLATE}


@router.post("/api/trmnl/settings")
async def trmnl_settings(request: Request):
    body = await request.json()
    cur = dict(config.CFG.get("trmnl") or {})
    if "uuid" in body:
        cur["uuid"] = str(body["uuid"]).strip()[:80]
    if "interval_s" in body:
        cur["interval_s"] = max(MIN_INTERVAL_S, min(3600, int(body["interval_s"])))
    if "enabled" in body:
        cur["enabled"] = bool(body["enabled"])
    config.CFG["trmnl"] = cur
    config.save(config.CFG)
    return {"status": "success", **_cfg(), "has_uuid": bool(cur.get("uuid"))}


@router.post("/api/trmnl/push")
async def trmnl_push():
    """Send the screen now — the 'test' button."""
    import asyncio
    return {"status": "success", **await asyncio.to_thread(push)}
