#!/usr/bin/env python3
"""Bearer token for the Superset MCP server (https://superset.swiftdrive-tech.ru/mcp).

Keycloak realm `infra`, public client `superset-mcp`, Device Authorization Grant.
First run opens a browser login (realm 2FA applies); after that the cached
refresh token renews silently until the Keycloak session ends.

  mcp-token.py            print the access token
  mcp-token.py --header   print {"Authorization": "Bearer ..."} (Claude Code headersHelper)
  mcp-token.py --logout   drop the cached token
"""
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

ISSUER = os.environ.get("SUPERSET_MCP_ISSUER", "https://auth.swiftdrive-tech.ru/realms/infra")
CLIENT_ID = os.environ.get("SUPERSET_MCP_CLIENT_ID", "superset-mcp")
CACHE = os.path.expanduser("~/.cache/superset-mcp/token.json")
MIN_TTL = 300  # renew when fewer seconds than this remain


def post(path, **form):
    body = urllib.parse.urlencode({"client_id": CLIENT_ID, **form}).encode()
    req = urllib.request.Request(f"{ISSUER}/protocol/openid-connect/{path}", body)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.load(resp)
    except urllib.error.HTTPError as e:
        try:
            return json.load(e)
        except ValueError:
            return {"error": f"HTTP {e.code}"}
    except urllib.error.URLError as e:
        sys.exit(f"superset-mcp: {ISSUER} unreachable: {e.reason}")


def save(tok):
    now = time.time()
    tok["expires_at"] = now + tok["expires_in"]
    tok["refresh_expires_at"] = now + tok.get("refresh_expires_in", 0)
    cache_dir = os.path.dirname(CACHE)
    os.makedirs(cache_dir, mode=0o700, exist_ok=True)
    os.chmod(cache_dir, 0o700)
    tmp = f"{CACHE}.{os.getpid()}"
    fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w") as f:
        json.dump(tok, f)
    os.replace(tmp, CACHE)  # concurrent headersHelper runs never see a torn file
    return tok


def load():
    try:
        with open(CACHE) as f:
            return json.load(f)
    except (OSError, ValueError):
        return None


def device_login():
    dev = post("auth/device")
    if "device_code" not in dev:
        sys.exit(f"device authorization failed: {dev}")
    url = dev.get("verification_uri_complete") or dev["verification_uri"]
    print(f"Superset MCP login: open {url} (code {dev['user_code']})", file=sys.stderr)
    if sys.platform == "darwin":
        subprocess.run(["open", url], check=False)
    interval = dev.get("interval", 5)
    deadline = time.time() + dev.get("expires_in", 600)
    while time.time() < deadline:
        time.sleep(interval)
        tok = post("token", grant_type="urn:ietf:params:oauth:grant-type:device_code",
                   device_code=dev["device_code"])
        if "access_token" in tok:
            return save(tok)
        err = tok.get("error")
        if err == "slow_down":
            interval += 5
        elif err != "authorization_pending":
            sys.exit(f"login failed: {tok}")
    sys.exit("login timed out")


def token():
    tok = load()
    now = time.time()
    if tok and tok["expires_at"] - now > MIN_TTL:
        return tok
    if tok and tok.get("refresh_token") and tok["refresh_expires_at"] > now:
        new = post("token", grant_type="refresh_token", refresh_token=tok["refresh_token"])
        if "access_token" in new:
            return save(new)
    if not sys.stdin.isatty() and not sys.stderr.isatty():
        # headersHelper runs non-interactively: don't hang on a login nobody sees
        sys.exit("superset-mcp: not logged in, run mcp-token.py in a terminal")
    return device_login()


def main():
    arg = sys.argv[1] if len(sys.argv) > 1 else ""
    if arg == "--logout":
        if os.path.exists(CACHE):
            os.remove(CACHE)
        return
    access = token()["access_token"]
    print(json.dumps({"Authorization": f"Bearer {access}"}) if arg == "--header" else access)


if __name__ == "__main__":
    main()
