"""
Exercise 2: dependency injection like FastAPI's Depends (medium)

Implement solve(fn, overrides=None) that calls `fn`, resolving parameters whose default is Depends(dep):
  - Resolve sub-dependencies recursively (a dependency can itself have Depends parameters).
  - Cache each dependency function's result for the duration of ONE solve() call (same function → same value).
  - If a dependency is a generator function, take its first yielded value; after `fn` returns, close all
    generators in REVERSE order of creation so their cleanup code (after `yield`) runs.
  - `overrides` maps a dependency function to a replacement function (like app.dependency_overrides).
Use inspect.signature and inspect.isgeneratorfunction.
"""

import inspect


class Depends:
    def __init__(self, dependency):
        self.dependency = dependency


def solve(fn, overrides=None):
    return fn()  # TODO


# --- self-check: do not modify below this line ---
log = []
created = {"settings": 0}


def get_settings():
    created["settings"] += 1
    return {"dsn": "sqlite://"}


def get_db(settings=Depends(get_settings)):
    log.append("open " + settings["dsn"])
    yield "db-session"
    log.append("close db")


def get_user(db=Depends(get_db), settings=Depends(get_settings)):
    return "ana via " + db


def endpoint(user=Depends(get_user), db=Depends(get_db)):
    log.append("handler")
    return (user, db)

checks = []
checks.append(("resolves nested dependencies", solve(endpoint), ("ana via db-session", "db-session")))
checks.append(("generator setup, handler, teardown order", list(log), ["open sqlite://", "handler", "close db"]))
checks.append(("cached within a request", created["settings"], 1))
log.clear()


def fake_db():
    return "fake-db"

checks.append(("overrides", solve(endpoint, {get_db: fake_db}), ("ana via fake-db", "fake-db")))
checks.append(("override skips real db", list(log), ["handler"]))

passed = 0
for label, actual, expected in checks:
    ok = actual == expected
    passed += ok
    print(f"{'PASS' if ok else 'FAIL'}: {label} (got {actual!r}, expected {expected!r})")

print("PASS" if passed == len(checks) else "FAIL")
