from datetime import timedelta

import jwt
import pytest

from orders_api.db import utcnow

pytestmark = pytest.mark.anyio


async def test_login_returns_a_bearer_token(client, asha):
    me = await client.get("/v1/me", headers=asha)
    assert me.json() == {"id": 1, "email": "asha@example.com", "role": "customer"}


async def test_wrong_password_and_unknown_user_look_the_same(client):
    wrong = await client.post("/v1/auth/token", data={"username": "asha@example.com", "password": "nope"})
    unknown = await client.post("/v1/auth/token", data={"username": "who@example.com", "password": "nope"})
    assert wrong.status_code == unknown.status_code == 401
    assert wrong.json()["detail"] == unknown.json()["detail"]


async def test_missing_token_gets_401_with_www_authenticate(client):
    r = await client.get("/v1/me")
    assert r.status_code == 401
    assert r.headers["www-authenticate"] == "Bearer"
    assert r.headers["content-type"] == "application/problem+json"


@pytest.mark.parametrize("tamper", ["alg-none", "wrong-secret", "expired", "wrong-audience"])
async def test_forged_or_stale_tokens_are_rejected(client, settings, tamper):
    claims = {"sub": "3", "email": "admin@example.com", "role": "admin", "iss": settings.jwt_issuer,
              "aud": settings.jwt_audience, "iat": utcnow(), "exp": utcnow() + timedelta(minutes=5)}
    secret = settings.jwt_secret.get_secret_value()
    if tamper == "alg-none":
        token = jwt.encode(claims, None, algorithm="none")
    elif tamper == "wrong-secret":
        token = jwt.encode(claims, "x" * 40, algorithm="HS256")
    elif tamper == "expired":
        token = jwt.encode(claims | {"exp": utcnow() - timedelta(seconds=1)}, secret, algorithm="HS256")
    else:
        token = jwt.encode(claims | {"aud": "someone-else"}, secret, algorithm="HS256")
    r = await client.get("/v1/me", headers={"Authorization": f"Bearer {token}"})
    assert r.status_code == 401


async def test_login_is_rate_limited_per_ip_and_account(client, settings):
    statuses = []
    for _ in range(settings.login_attempts_per_minute + 1):
        r = await client.post("/v1/auth/token", data={"username": "asha@example.com", "password": "guess"})
        statuses.append(r.status_code)
    assert statuses == [401] * settings.login_attempts_per_minute + [429]
    assert int(r.headers["retry-after"]) > 0


async def test_validation_errors_never_echo_the_password(client):
    r = await client.post("/v1/auth/token", data={"password": "hunter2-secret"})
    assert r.status_code == 422
    assert "hunter2-secret" not in r.text


async def test_successful_logins_do_not_use_up_the_limit(client, settings):
    for _ in range(settings.login_attempts_per_minute + 2):
        r = await client.post("/v1/auth/token", data={"username": "asha@example.com", "password": "brew-demo-pass"})
        assert r.status_code == 200
