#!/usr/bin/env python3
"""douanier — admin CLI for the Audio-Intelligence API.

The ONLY way tokens are created (no public signup in MVP). Run with the service
venv so it shares config/db:

    ~/.virtualenvs/douanier/bin/python douanier.py token mint --account hexa --scopes '*' --ttl 31
    ~/.virtualenvs/douanier/bin/python douanier.py token list
    ~/.virtualenvs/douanier/bin/python douanier.py token revoke 3
    ~/.virtualenvs/douanier/bin/python douanier.py db init
"""
import argparse
import datetime as dt
import sys

import auth
import cache
import db


def _fmt_ts(ts):
    if ts is None:
        return "never"
    return dt.datetime.fromtimestamp(ts).strftime("%Y-%m-%d")


def cmd_token_mint(a):
    ttl = None if a.ttl is None else float(a.ttl)
    plaintext = auth.mint(a.account, scopes=a.scopes, ttl_days=ttl,
                          unlimited=not a.metered, notes=a.notes)
    exp = "never" if ttl is None else f"{int(ttl)} days"
    print(f"⛵ minted token for account '{a.account}'  (scopes: {a.scopes}, "
          f"expires: {exp}, quota: {'metered' if a.metered else 'unlimited'})")
    print(f"\n  {plaintext}\n")
    print("  ↑ shown ONCE — store it now; only its hash is kept.")


def cmd_token_list(_a):
    rows = auth.list_tokens()
    if not rows:
        print("(no tokens — mint one with: token mint --account <name>)")
        return
    print(f"{'id':>3}  {'account':<12} {'prefix':<8} {'scopes':<10} "
          f"{'expires':<11} {'quota':<9} state")
    for r in rows:
        state = "REVOKED" if r["revoked"] else "active"
        quota = "unlimited" if r["quota_json"] is None else "metered"
        print(f"{r['id']:>3}  {r['account']:<12} {r['prefix']+'…':<8} "
              f"{r['scopes']:<10} {_fmt_ts(r['expires_at']):<11} {quota:<9} {state}")


def cmd_token_revoke(a):
    print("revoked" if auth.revoke(a.id) else f"no token #{a.id}")


def cmd_db_init(_a):
    db.init()
    print(f"initialized {db.config.DB_PATH}")


def cmd_cache_stats(_a):
    rows = cache.stats()
    if not rows:
        print("(cache empty)")
        return
    print(f"{'kind':<10} {'entries':>8} {'bytes':>12} {'hits':>8}")
    for r in rows:
        print(f"{r['kind']:<10} {r['n']:>8} {r['bytes']:>12} {r['hits']:>8}")


def cmd_cache_gc(a):
    res = cache.gc(a.kind, int(a.max_bytes))
    print(f"evicted {res['evicted']} entries, freed {res['freed']} bytes from '{a.kind}'")


def main(argv=None):
    p = argparse.ArgumentParser(prog="douanier", description=__doc__)
    sub = p.add_subparsers(dest="group", required=True)

    tok = sub.add_parser("token", help="manage API tokens").add_subparsers(
        dest="cmd", required=True)
    m = tok.add_parser("mint", help="create a token (prints plaintext once)")
    m.add_argument("--account", required=True)
    m.add_argument("--scopes", default="*", help="space-separated, or '*' (default)")
    m.add_argument("--ttl", default="31", help="days until expiry, or 'never'")
    m.add_argument("--metered", action="store_true", help="enforce quota (default: unlimited)")
    m.add_argument("--notes", default="")
    m.set_defaults(func=cmd_token_mint)
    tok.add_parser("list", help="list tokens").set_defaults(func=cmd_token_list)
    rv = tok.add_parser("revoke", help="revoke a token by id")
    rv.add_argument("id", type=int)
    rv.set_defaults(func=cmd_token_revoke)

    d = sub.add_parser("db", help="database admin").add_subparsers(dest="cmd", required=True)
    d.add_parser("init", help="create tables").set_defaults(func=cmd_db_init)

    ca = sub.add_parser("cache", help="inspect/evict the content cache").add_subparsers(
        dest="cmd", required=True)
    ca.add_parser("stats", help="per-kind entry/byte/hit counts").set_defaults(func=cmd_cache_stats)
    g = ca.add_parser("gc", help="evict coldest entries of a kind down to a byte cap")
    g.add_argument("kind")
    g.add_argument("max_bytes")
    g.set_defaults(func=cmd_cache_gc)

    args = p.parse_args(argv)
    if getattr(args, "ttl", None) in ("never", "none", "None"):
        args.ttl = None
    args.func(args)


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