"""
Shared helpers for set-channel.py and search-game.py.

Stdlib-only on purpose — the bot dir intentionally avoids a venv. If a third
caller appears that needs richer behavior (retries with backoff, async, etc.),
revisit; until then minimal urllib is fine.

Auth model: every Helix call goes through `auth_call`, which runs the request,
refreshes the access token via twitchtokengenerator on 401, persists the new
tokens to .env.ophi118, mutates the in-memory env dict, and retries once.
Callers stay agnostic about whether a refresh happened.
"""

import json
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

HELIX        = "https://api.twitch.tv/helix"
REFRESH_URL  = "https://twitchtokengenerator.com/api/refresh/{refresh}"
ENV_FILE     = Path(__file__).resolve().parent / ".env.ophi118"


# ─── env file I/O ───────────────────────────────────────────────────────────

def load_env(path: Path = ENV_FILE) -> dict:
    out = {}
    if not path.exists():
        return out
    for raw in path.read_text().splitlines():
        line = raw.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, _, v = line.partition("=")
        out[k.strip()] = v.strip().strip('"').strip("'")
    return out


def update_env(updates: dict, path: Path = ENV_FILE) -> None:
    """Atomic in-place rewrite of KEY=VALUE lines, comments preserved."""
    lines = path.read_text().splitlines() if path.exists() else []
    seen, out_lines = set(), []
    for line in lines:
        s = line.strip()
        if s and not s.startswith("#") and "=" in s:
            k = s.split("=", 1)[0].strip()
            if k in updates:
                out_lines.append(f"{k}={updates[k]}")
                seen.add(k)
                continue
        out_lines.append(line)
    for k, v in updates.items():
        if k not in seen:
            out_lines.append(f"{k}={v}")
    tmp = path.with_suffix(path.suffix + ".tmp")
    tmp.write_text("\n".join(out_lines) + "\n")
    tmp.replace(path)


# ─── HTTP ───────────────────────────────────────────────────────────────────

def http(method: str, url: str, *, headers=None, body=None):
    """Returns (status, parsed_json_or_text). Never raises on HTTP errors —
    surfaces them as the status code so callers can branch on 401 cleanly."""
    data = body.encode("utf-8") if isinstance(body, str) else body
    req  = urllib.request.Request(url, method=method, headers=headers or {}, data=data)
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            raw = r.read().decode("utf-8")
            try:    return r.status, (json.loads(raw) if raw else None)
            except json.JSONDecodeError: return r.status, raw
    except urllib.error.HTTPError as e:
        raw = e.read().decode("utf-8", errors="replace")
        try:    return e.code, (json.loads(raw) if raw else None)
        except json.JSONDecodeError: return e.code, raw


# ─── auth ───────────────────────────────────────────────────────────────────

def refresh_token(env: dict) -> dict:
    """Mint new tokens via twitchtokengenerator. Returns the merge dict for
    `update_env` — caller persists. Raises on refresh failure (no point
    retrying — the refresh token is dead, the user must re-generate)."""
    refresh = env["TWITCH_REFRESH_TOKEN"]
    url = REFRESH_URL.format(refresh=urllib.parse.quote(refresh, safe=""))
    status, body = http("GET", url)
    if status != 200 or not isinstance(body, dict) or not body.get("success"):
        raise RuntimeError(f"token refresh failed (status={status}): {body}")
    return {
        "TWITCH_ACCESS_TOKEN":  body["token"],
        "TWITCH_REFRESH_TOKEN": body["refresh"],
    }


def auth_call(method: str, url: str, env: dict, *, body=None):
    """Authenticated Helix request with 401 → refresh → retry. Mutates `env`
    in place on refresh so subsequent calls in the same process see the new
    token without another reload."""
    def headers_for(tok):
        h = {"Authorization": f"Bearer {tok}", "Client-Id": env["TWITCH_CLIENT_ID"]}
        if body is not None:
            h["Content-Type"] = "application/json"
        return h

    status, resp = http(method, url, headers=headers_for(env["TWITCH_ACCESS_TOKEN"]), body=body)
    if status == 401:
        new = refresh_token(env)
        update_env(new)
        env.update(new)
        status, resp = http(method, url, headers=headers_for(env["TWITCH_ACCESS_TOKEN"]), body=body)
    return status, resp
