"""Every route, and what actually guards it.

Read from the running app's dependency tree rather than from the source, so a
guard that is declared but shadowed, or applied at the router rather than the
endpoint, is reported as the app really resolves it.
"""
import csv

from fastapi.routing import APIRoute

from app.main import app

GUARDS = {
    "get_current_user": "signed-in",
    "get_current_user_optional": "optional",
    "require_moderator": "moderator",
    "require_admin": "admin",
    "get_db": None,
}

WRITES = {"POST", "PUT", "PATCH", "DELETE"}


def guards_of(route):
    """Names of the auth dependencies this route resolves, at any depth."""
    found, seen = [], set()

    def walk(dep):
        if id(dep) in seen:
            return
        seen.add(id(dep))
        name = getattr(dep.call, "__name__", "") if dep.call else ""
        if name in GUARDS and GUARDS[name]:
            found.append(GUARDS[name])
        for child in dep.dependencies:
            walk(child)

    walk(route.dependant)
    return found


rows = []
for route in app.routes:
    if not isinstance(route, APIRoute):
        continue
    for method in sorted(route.methods - {"HEAD", "OPTIONS"}):
        found = guards_of(route)
        guard = ("admin" if "admin" in found else
                 "moderator" if "moderator" in found else
                 "signed-in" if "signed-in" in found else
                 "optional" if "optional" in found else "NONE")
        rows.append({
            "method": method, "path": route.path, "guard": guard,
            "writes": "yes" if method in WRITES else "",
            "name": route.name,
        })

rows.sort(key=lambda r: (r["guard"] != "NONE", r["path"]))
with open("/app/routes.csv", "w", newline="") as handle:
    writer = csv.DictWriter(handle, fieldnames=["method", "path", "guard", "writes", "name"])
    writer.writeheader()
    writer.writerows(rows)

from collections import Counter
print("total routes:", len(rows))
print(Counter(r["guard"] for r in rows))
print("\n--- UNGUARDED THAT WRITE ---")
for r in rows:
    if r["guard"] == "NONE" and r["writes"]:
        print(f'  {r["method"]:6} {r["path"]}   ({r["name"]})')
print("\n--- UNGUARDED READS ---")
for r in rows:
    if r["guard"] == "NONE" and not r["writes"]:
        print(f'  {r["method"]:6} {r["path"]}   ({r["name"]})')
