import hashlib
from urllib.parse import parse_qs, urlparse

from app.auth import pkce_pair


def test_pkce_pair_is_hex_sha256_of_an_unreserved_verifier():
    verifier, challenge = pkce_pair()
    assert 43 <= len(verifier) <= 128
    assert all(c.isalnum() or c in "-._~" for c in verifier)
    assert challenge == hashlib.sha256(verifier.encode("ascii")).hexdigest()


def test_auth_start_sends_challenge_and_keeps_verifier(client):
    r = client.get("/auth/start")
    assert r.status_code == 302
    query = parse_qs(urlparse(r.headers["Location"]).query)
    assert query["code_challenge_method"] == ["S256"]
    with client.session_transaction() as s:
        verifier = s["oauth_code_verifier"]
    assert query["code_challenge"] == [hashlib.sha256(verifier.encode("ascii")).hexdigest()]
    assert verifier not in r.headers["Location"]


def test_callback_exchanges_code_with_the_flow_verifier(client, monkeypatch):
    seen = {}

    def fake_exchange(code, key, secret, redirect_uri, code_verifier=None):
        seen.update(code=code, verifier=code_verifier)
        return {"access_token": "a", "refresh_token": "r", "open_id": "oid", "expires_in": 86400, "scope": "user.info.basic"}

    monkeypatch.setattr("app.auth.exchange_code", fake_exchange)
    monkeypatch.setattr("app.auth.get_user_info", lambda token: {"display_name": "Tester"})
    client.get("/auth/start")
    with client.session_transaction() as s:
        state, verifier = s["oauth_state"], s["oauth_code_verifier"]
    r = client.get(f"/auth/callback?code=c1&state={state}")
    assert r.status_code == 302
    assert "/dashboard" in r.headers["Location"]
    assert seen == {"code": "c1", "verifier": verifier}


def test_callback_without_a_verifier_is_refused(client, monkeypatch):
    monkeypatch.setattr("app.auth.exchange_code", lambda *a, **k: (_ for _ in ()).throw(AssertionError("must not exchange")))
    with client.session_transaction() as s:
        s["oauth_state"] = "st"
    r = client.get("/auth/callback?code=c1&state=st")
    assert r.status_code == 302
    assert "/login" in r.headers["Location"]
