"""Unit tests for password hashing, JWT tokens, and API key generation."""

from __future__ import annotations

import uuid

import jwt
import pytest
from legate.config import get_settings
from legate.security.api_keys import KEY_PREFIX, generate_api_key, hash_api_key
from legate.security.passwords import hash_password, verify_password
from legate.security.tokens import (
    TokenError,
    create_access_token,
    create_refresh_token,
    decode_token,
)


def test_password_hash_roundtrip():
    hashed = hash_password("correct horse battery staple")
    assert hashed != "correct horse battery staple"
    assert verify_password("correct horse battery staple", hashed)
    assert not verify_password("wrong password", hashed)
    assert not verify_password("anything", None)


def test_access_token_carries_workspace():
    user_id = uuid.uuid4()
    ws_id = uuid.uuid4()
    token = create_access_token(user_id, ws_id)
    payload = decode_token(token, expected_type="access")
    assert payload.user_id == user_id
    assert payload.workspace_id == ws_id
    assert payload.token_type == "access"


def test_refresh_token_has_no_workspace_and_wrong_type_rejected():
    user_id = uuid.uuid4()
    token = create_refresh_token(user_id)
    payload = decode_token(token, expected_type="refresh")
    assert payload.workspace_id is None
    # A refresh token must not be accepted where an access token is required.
    with pytest.raises(TokenError):
        decode_token(token, expected_type="access")


def test_expired_token_rejected():
    settings = get_settings()
    claims = {
        "sub": str(uuid.uuid4()),
        "type": "access",
        "ws": None,
        "exp": 1_000_000,  # far in the past
        "jti": "x",
    }
    token = jwt.encode(claims, settings.legate_secret_key, algorithm=settings.jwt_algorithm)
    with pytest.raises(TokenError):
        decode_token(token, expected_type="access")


def test_tampered_token_rejected():
    token = create_access_token(uuid.uuid4(), uuid.uuid4())
    forged = jwt.encode(
        {"sub": "x", "type": "access"},
        "a-different-secret-key-of-sufficient-length-32+",
        algorithm="HS256",
    )
    assert token != forged
    with pytest.raises(TokenError):
        decode_token(forged, expected_type="access")


def test_api_key_generation_is_unique_and_hashed():
    a = generate_api_key()
    b = generate_api_key()
    assert a.plaintext.startswith(KEY_PREFIX)
    assert a.plaintext != b.plaintext
    assert a.hashed_key == hash_api_key(a.plaintext)
    assert a.hashed_key != a.plaintext
    assert a.prefix and a.plaintext.startswith(a.prefix)


@pytest.mark.parametrize("garbage", ["not-a-jwt", "", "a.b.c", "Bearer x"])
def test_decode_rejects_garbage(garbage):
    with pytest.raises(TokenError):
        decode_token(garbage, expected_type="access")
