"""End-to-end test of roles and tenancy with Flask's test client against a throwaway database."""
import os, sys, tempfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT); os.chdir(ROOT)
os.environ["DB_PATH"] = os.path.join(tempfile.mkdtemp(), "test.db")
os.environ.update(ADMIN_USERNAME="admin", ADMIN_PASSWORD="admin-pass-1", KID_USERNAME="yaseen", KID_PASSWORD="kid123",
                  PARENT_USERNAME="parent", PARENT_PASSWORD="parent123", KID_DISPLAY_NAME="Yaseen", PARENT_DISPLAY_NAME="Parent")
from app import app
import curriculum as C

def client(user, pw):
    c = app.test_client()
    r = c.post("/api/login", json=dict(username=user, password=pw)).get_json()
    assert r.get("ok"), (user, r)
    return c, r

ok = lambda r: (r.status_code == 200 and r.get_json().get("ok")) or (_ for _ in ()).throw(AssertionError(r.get_json()))
anon = app.test_client()
assert anon.get("/").status_code == 302 and anon.get("/api/me").status_code == 401
assert "error" in anon.post("/api/login", json=dict(username="yaseen", password="nope")).get_json()

adm, r = client("admin", "admin-pass-1"); assert r["home"] == "/admin"
par1, r = client("parent", "parent123"); assert r["home"] == "/parent"
kid1, r = client("yaseen", "kid123"); assert r["home"] == "/"
# role walls
assert kid1.get("/parent").status_code == 302 and kid1.get("/api/kids").status_code == 403
assert par1.get("/admin").status_code == 302 and par1.get("/api/admin/parents").status_code == 403
assert adm.get("/admin").status_code == 200 and par1.get("/parent").status_code == 200

# admin creates a second family with a limit of 1 kid
r = adm.post("/api/admin/parents", json=dict(username="mona", display_name="Mona", max_kids=1)).get_json(); assert r["ok"] and r["password"]
p2_id, p2_pw = r["parent"]["id"], r["password"]
assert "error" in adm.post("/api/admin/parents", json=dict(username="mona", display_name="X", password="abcdef")).get_json()   # duplicate
assert "error" in adm.post("/api/admin/parents", json=dict(username="Bad Name", display_name="X", password="abcdef")).get_json()
par2, _ = client("mona", p2_pw)
r = par2.post("/api/kids", json=dict(username="sara", display_name="Sara", avatar="🦊")).get_json(); assert r["ok"], r
sara_id, sara_pw = r["kid"]["id"], r["password"]
r = par2.post("/api/kids", json=dict(username="omar", display_name="Omar")).get_json(); assert "error" in r, "limit of 1 must block"
# admin raises the limit, adds a kid to that parent
assert "error" in adm.post(f"/api/admin/parents/{p2_id}", json=dict(max_kids=0)).get_json(), "cannot go below current kids"
ok(adm.post(f"/api/admin/parents/{p2_id}", json=dict(max_kids=2)))
r = adm.post("/api/kids", json=dict(parent_id=p2_id, username="omar", display_name="Omar", password="omar-pass")).get_json(); assert r["ok"], r
omar_id = r["kid"]["id"]
assert len(par2.get("/api/kids").get_json()["kids"]) == 2

# tenant isolation: parent 1 cannot see or touch parent 2's kids
yaseen_id = par1.get("/api/kids").get_json()["kids"][0]["id"]
for url in (f"/api/kid/{sara_id}/overview", f"/api/kid/{sara_id}/history"):
    assert par1.get(url).status_code == 403, url
for url in (f"/api/kids/{sara_id}", f"/api/kids/{sara_id}/password", f"/api/kids/{sara_id}/delete", f"/api/kid/{sara_id}/reset"):
    assert par1.post(url, json={}).status_code == 403, url
assert par2.get(f"/api/kid/{yaseen_id}/overview").status_code == 403
assert all(k["id"] != sara_id for k in par1.get("/api/kids").get_json()["kids"])
assert par1.post("/api/kids", json=dict(parent_id=p2_id, username="intruder", display_name="X")).get_json()["kid"]["parent_id"] != p2_id
assert adm.get(f"/api/kid/{sara_id}/overview").status_code == 200

# kid learns; parent sees it; resets work
sara, _ = client("sara", sara_pw)
for a in C.LESSONS["u1-ab"]["activities"]:
    sara.post("/api/complete", json=dict(activity_id=a["id"], score=5, total=5, seconds=40))
ov = par2.get(f"/api/kid/{sara_id}/overview").get_json(); assert ov["stats"]["done"] == 12 and "first" in ov["stats"]["badges"]
assert len(par2.get(f"/api/kid/{sara_id}/history").get_json()["history"]) == 12
ok(par2.post(f"/api/kid/{sara_id}/reset", json=dict(lesson_id="u1-ab")))
assert par2.get(f"/api/kid/{sara_id}/overview").get_json()["stats"]["done"] == 0
# password reset by the parent: old password stops working, new one works
r = par2.post(f"/api/kids/{sara_id}/password", json={}).get_json(); assert r["ok"]
assert "error" in app.test_client().post("/api/login", json=dict(username="sara", password=sara_pw)).get_json()
client("sara", r["password"])
# avatar, unlock, suspend
ok(sara.post("/api/me/avatar", json=dict(avatar="🦄"))); assert sara.get("/api/me").get_json()["avatar"] == "🦄"
assert "error" in sara.post("/api/me/avatar", json=dict(avatar="<b>")).get_json()
ok(par2.post(f"/api/kids/{sara_id}", json=dict(unlock_all=True))); assert sara.get("/api/me").get_json()["unlock_all"] is True
ok(adm.post(f"/api/admin/parents/{p2_id}", json=dict(active=False)))
assert sara.get("/api/me").status_code == 401, "suspended parent blocks its kids at once"
assert "error" in app.test_client().post("/api/login", json=dict(username="mona", password=p2_pw)).get_json()
ok(adm.post(f"/api/admin/parents/{p2_id}", json=dict(active=True)))
# own password
assert "error" in par1.post("/api/account/password", json=dict(old="wrong", new="newpass1")).get_json()
ok(par1.post("/api/account/password", json=dict(old="parent123", new="newpass1"))); client("parent", "newpass1")
# admin deletes the second family: parent, kids and their progress go
r = adm.post(f"/api/admin/parents/{p2_id}/delete", json={}).get_json(); assert r["deleted"] == 3
assert "error" in app.test_client().post("/api/login", json=dict(username="omar", password="omar-pass")).get_json()
s = adm.get("/api/admin/summary").get_json(); assert s["parents"] == 1 and s["kids"] == 2, s   # yaseen + the "intruder" that landed under parent 1
# course levels: only available levels may be assigned
r = par1.post("/api/kids", json=dict(username="lvl.two", display_name="L2", course_level=2)).get_json(); assert "error" in r and "غير متاح" in r["error"], r
r = par1.post("/api/kids", json=dict(username="lvl.bad", display_name="LX", course_level=9)).get_json(); assert "error" in r, r
r = par1.post("/api/kids", json=dict(username="lvl.one", display_name="L1", course_level=1)).get_json(); assert r["ok"] and r["kid"]["course_level"] == 1, r
assert "error" in par1.post(f"/api/kids/{r['kid']['id']}", json=dict(course_level=3)).get_json()
lv = par1.get("/api/kids").get_json()["levels"]; assert [l["available"] for l in lv] == [True, False, False, True, False, False, False], lv
k1, _ = client("lvl.one", r["password"]); cu = k1.get("/api/curriculum").get_json()
assert cu["course"]["id"] == 1 and len(cu["units"]) == 3, cu["course"]
ov = par1.get(f"/api/kid/{r['kid']['id']}/overview").get_json(); assert ov["course"]["title_ar"] == "المستوى الأول"
par1.post(f"/api/kids/{r['kid']['id']}/delete", json={})
# level 4 (age 11, junior band): its own units, its activities only
r4 = par1.post("/api/kids", json=dict(username="lvl.four", display_name="L4", course_level=4)).get_json(); assert r4["ok"], r4
k4, _ = client("lvl.four", r4["password"]); cu4 = k4.get("/api/curriculum").get_json()
assert cu4["course"]["id"] == 4 and cu4["band"] == "junior" and all(u["id"].startswith("l4-") for u in cu4["units"]), cu4["course"]
a4 = cu4["units"][0]["lessons"][1]["activities"][1]
assert "result" in k4.post("/api/complete", json=dict(activity_id=a4["id"], score=8, total=10, seconds=40)).get_json()
assert {b["id"] for b in cu4["badges"]} >= {"first", "l4_conj"} and "alphabet" not in {b["id"] for b in cu4["badges"]}
assert "error" in k4.post("/api/complete", json=dict(activity_id=cu["units"][0]["lessons"][0]["activities"][0]["id"], score=1, total=1, seconds=5)).get_json()
par1.post(f"/api/kids/{r4['kid']['id']}/delete", json={})
print("ALL SMOKE TESTS PASSED")
