"""Unit tests for jwt_utils (V2 sid/jti claims)."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone

import jwt
import pytest

from agentkit.server.auth.jwt_utils import (
    ACCESS_TOKEN_TTL,
    JWT_ALGORITHM,
    REFRESH_TOKEN_TTL,
    REFRESH_TOKEN_TTL_REMEMBER_ME,
    create_token_pair,
    verify_token,
)

SECRET = "test-secret-with-at-least-32-bytes-1234"


def test_create_token_pair_has_required_v1_claims():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    for token in (pair.access_token, pair.refresh_token):
        payload = jwt.decode(token, SECRET, algorithms=[JWT_ALGORITHM])
        assert payload["sub"] == "u-1"
        assert payload["username"] == "alice"
        assert payload["role"] == "member"
        assert payload["iat"] and payload["exp"]


def test_v1_pair_has_no_sid_or_jti():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    access = jwt.decode(pair.access_token, SECRET, algorithms=[JWT_ALGORITHM])
    refresh = jwt.decode(pair.refresh_token, SECRET, algorithms=[JWT_ALGORITHM])
    assert "sid" not in access
    assert "jti" not in access
    assert "sid" not in refresh


def test_v2_pair_includes_sid_on_both_tokens_and_jti_on_access():
    pair = create_token_pair(
        user_id="u-1",
        username="alice",
        role="member",
        secret=SECRET,
        session_id="sess-abc",
    )
    access = jwt.decode(pair.access_token, SECRET, algorithms=[JWT_ALGORITHM])
    refresh = jwt.decode(pair.refresh_token, SECRET, algorithms=[JWT_ALGORITHM])
    assert access["sid"] == "sess-abc"
    assert refresh["sid"] == "sess-abc"
    assert access["jti"] and isinstance(access["jti"], str)
    # Refresh intentionally has no jti — rotation uses the token hash.
    assert "jti" not in refresh


def test_refresh_token_type_is_refresh():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    refresh = jwt.decode(pair.refresh_token, SECRET, algorithms=[JWT_ALGORITHM])
    assert refresh["type"] == "refresh"


def test_access_token_type_is_access():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    access = jwt.decode(pair.access_token, SECRET, algorithms=[JWT_ALGORITHM])
    assert access["type"] == "access"


def test_default_refresh_ttl_is_7_days():
    now = datetime(2026, 6, 20, 0, 0, 0, tzinfo=timezone.utc)
    pair = create_token_pair(
        user_id="u-1",
        username="alice",
        role="member",
        secret=SECRET,
        now=now,
    )
    assert (pair.access_expires_at - now) == ACCESS_TOKEN_TTL
    assert (pair.refresh_expires_at - now) == REFRESH_TOKEN_TTL


def test_remember_me_extends_refresh_ttl_to_30_days():
    now = datetime(2026, 6, 20, 0, 0, 0, tzinfo=timezone.utc)
    pair = create_token_pair(
        user_id="u-1",
        username="alice",
        role="member",
        secret=SECRET,
        now=now,
        remember_me=True,
    )
    assert (pair.refresh_expires_at - now) == REFRESH_TOKEN_TTL_REMEMBER_ME


def test_verify_token_accepts_access_and_refresh_by_default():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    a = verify_token(pair.access_token, SECRET)
    r = verify_token(pair.refresh_token, SECRET)
    assert a["type"] == "access"
    assert r["type"] == "refresh"


def test_verify_token_expected_type_filters_other_type():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    with pytest.raises(jwt.InvalidTokenError):
        verify_token(pair.access_token, SECRET, expected_type="refresh")
    with pytest.raises(jwt.InvalidTokenError):
        verify_token(pair.refresh_token, SECRET, expected_type="access")


def test_verify_token_rejects_expired():
    # Use a "now" far in the past so the access token is definitely expired.
    past = datetime.now(timezone.utc) - timedelta(hours=1)
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET, now=past
    )
    with pytest.raises(jwt.ExpiredSignatureError):
        verify_token(pair.access_token, SECRET)


def test_verify_token_rejects_wrong_secret():
    pair = create_token_pair(
        user_id="u-1", username="alice", role="member", secret=SECRET
    )
    with pytest.raises(jwt.InvalidTokenError):
        verify_token(pair.access_token, "other-secret")


def test_create_token_pair_rejects_empty_secret():
    with pytest.raises(ValueError):
        create_token_pair(user_id="u-1", username="alice", role="member", secret="")
