from __future__ import annotations

import secrets
from datetime import timedelta

import pytest

from services.security import (
    JWTSettings,
    TokenDecodeError,
    TokenExpiredError,
    TokenTypeMismatchError,
    create_access_token,
    create_refresh_token,
    decode_access_token,
    hash_password,
    verify_password,
)


@pytest.fixture()
def jwt_settings() -> JWTSettings:
    """Provide a unique JWTSettings instance per test with random secret."""
    return JWTSettings(secret_key=secrets.token_urlsafe(32))


def test_hash_password_round_trip() -> None:
    password = secrets.token_urlsafe(16)
    hashed = hash_password(password)

    assert hashed != password
    assert verify_password(password, hashed)
    assert not verify_password("incorrect", hashed)


def test_verify_password_handles_malformed_hash() -> None:
    assert not verify_password("secret", "not-a-valid-hash")


def test_access_token_roundtrip(jwt_settings: JWTSettings) -> None:
    token = create_access_token(
        "user-id-123",
        jwt_settings,
        scopes=("read", "write"),
        extra_claims={"custom": "value"},
    )

    payload = decode_access_token(token, jwt_settings)

    assert payload.sub == "user-id-123"
    assert payload.type == "access"
    assert payload.scopes == ["read", "write"]


def test_refresh_token_type_mismatch(jwt_settings: JWTSettings) -> None:
    token = create_refresh_token("user-id-456", jwt_settings)

    with pytest.raises(TokenTypeMismatchError):
        decode_access_token(token, jwt_settings)


def test_decode_expired_token(jwt_settings: JWTSettings) -> None:
    expired_token = create_access_token(
        "user-id-789",
        jwt_settings,
        expires_delta=timedelta(seconds=-5),
    )

    with pytest.raises(TokenExpiredError):
        decode_access_token(expired_token, jwt_settings)


def test_decode_tampered_token(jwt_settings: JWTSettings) -> None:
    token = create_access_token("user-id-321", jwt_settings)
    tampered = token[:-1] + ("a" if token[-1] != "a" else "b")

    with pytest.raises(TokenDecodeError):
        decode_access_token(tampered, jwt_settings)
