import time

import jwt
import pytest

from app.access.sessions import AUDIENCE, ISSUER, SessionService
from app.core.principal import Principal
from app.integrations.base import InvalidTokenError
from tests.conftest import SESSION_SECRET
from tests.tokens import meeting_token

PRINCIPAL = Principal(
    product="helixtahr", org_id="26", user_id="674", name="Test User", is_org_admin=True
)


def test_issued_session_verifies_back_to_the_same_caller():
    sessions = SessionService(SESSION_SECRET, 900)
    issued = sessions.issue(PRINCIPAL, "product-token")

    session = sessions.verify(issued.token)

    assert issued.expires_in == 900
    assert session.principal == PRINCIPAL
    assert session.product_token == "product-token"


def test_session_token_does_not_contain_the_product_token():
    issued = SessionService(SESSION_SECRET, 900).issue(PRINCIPAL, "product-token")

    claims = jwt.decode(issued.token, options={"verify_signature": False})
    assert "product-token" not in str(claims)


def test_each_session_is_separate():
    sessions = SessionService(SESSION_SECRET, 900)
    first = sessions.issue(PRINCIPAL, "token-1")
    second = sessions.issue(PRINCIPAL, "token-2")

    assert sessions.verify(first.token).product_token == "token-1"
    assert sessions.verify(second.token).product_token == "token-2"


def test_expired_session_is_refused():
    issued_long_ago = SessionService(SESSION_SECRET, 900, clock=lambda: time.time() - 1000)
    token = issued_long_ago.issue(PRINCIPAL, "product-token").token

    with pytest.raises(InvalidTokenError):
        SessionService(SESSION_SECRET, 900).verify(token)


def test_session_from_another_secret_is_refused():
    token = SessionService("another-secret-" + "0123456789abcdef" * 3, 900).issue(PRINCIPAL, "t")

    with pytest.raises(InvalidTokenError):
        SessionService(SESSION_SECRET, 900).verify(token.token)


def test_product_token_is_not_a_session():
    with pytest.raises(InvalidTokenError):
        SessionService(SESSION_SECRET, 900).verify(meeting_token())


@pytest.mark.parametrize("missing", ["product", "org", "name", "sid", "sub"])
def test_session_missing_a_claim_is_refused(missing):
    now = int(time.time())
    claims = {
        "iss": ISSUER,
        "aud": AUDIENCE,
        "sub": "674",
        "sid": "s1",
        "product": "helixtahr",
        "org": "26",
        "name": "Test User",
        "iat": now,
        "nbf": now,
        "exp": now + 900,
    }
    del claims[missing]
    token = jwt.encode(claims, SESSION_SECRET, algorithm="HS256")

    with pytest.raises(InvalidTokenError):
        SessionService(SESSION_SECRET, 900).verify(token)


def test_product_token_is_not_held_after_restart():
    token = SessionService(SESSION_SECRET, 900).issue(PRINCIPAL, "product-token").token

    # A new service (as after a restart) still trusts the signature but holds no product token.
    assert SessionService(SESSION_SECRET, 900).verify(token).product_token is None
