"""Signed-grant HMAC for password-protected share links: a grant proves the viewer unlocked a
token, rides in the file URL (a <video src> can't send a header), and expires."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace

from app.downloads.links import _GRANT_TTL, check_grant, is_expired, make_grant, new_token


def test_new_token_is_unguessable_and_unique():
    a, b = new_token(), new_token()
    assert a != b
    assert len(a) >= 30  # token_urlsafe(24) → ~32 chars


class TestGrant:
    def test_a_fresh_grant_verifies_for_its_own_token(self):
        grant = make_grant("tok", 0)
        assert check_grant("tok", grant, 0) is True

    def test_a_grant_does_not_verify_for_a_different_token(self):
        # The signature binds the token, so a grant minted for one link can't unlock another.
        assert check_grant("other", make_grant("tok", 0), 0) is False

    def test_a_tampered_signature_is_rejected(self):
        exp, _sig = make_grant("tok", 0).split(".", 1)
        assert check_grant("tok", f"{exp}.deadbeef", 0) is False

    def test_a_tampered_expiry_is_rejected(self):
        # Extending the exp field invalidates the signature (which covers token.exp.version).
        _exp, sig = make_grant("tok", 0).split(".", 1)
        future = int(datetime.now(timezone.utc).timestamp()) + 10 * _GRANT_TTL
        assert check_grant("tok", f"{future}.{sig}", 0) is False

    def test_an_expired_grant_is_rejected(self):
        past = datetime.now(timezone.utc).timestamp() - 2 * _GRANT_TTL
        assert check_grant("tok", make_grant("tok", 0, now=past), 0) is False

    def test_a_password_rotation_invalidates_an_old_grant(self):
        # A grant minted at version 0 must fail once the link's password_version has been bumped —
        # this is what makes changing a link password revoke outstanding grants immediately.
        grant = make_grant("tok", 0)
        assert check_grant("tok", grant, 0) is True
        assert check_grant("tok", grant, 1) is False

    def test_malformed_grants_are_rejected_not_crashed(self):
        for bad in [None, "", "no-dot", "notanumber.sig"]:
            assert check_grant("tok", bad, 0) is False


class TestIsExpired:
    def test_none_expiry_never_expires(self):
        assert is_expired(SimpleNamespace(expires_at=None)) is False

    def test_a_future_expiry_is_live(self):
        future = datetime.now(timezone.utc) + timedelta(hours=1)
        assert is_expired(SimpleNamespace(expires_at=future)) is False

    def test_a_past_expiry_is_expired(self):
        past = datetime.now(timezone.utc) - timedelta(hours=1)
        assert is_expired(SimpleNamespace(expires_at=past)) is True
