"""Tests for app.core.security — JWT, bcrypt, MFA/TOTP, token hashing."""

import hashlib
from datetime import datetime, timedelta, timezone

import pyotp
import pytest
from jose import JWTError, jwt

from app.config import get_settings
from app.core.security import (
    create_access_token,
    create_refresh_token,
    decode_access_token,
    generate_mfa_secret,
    get_mfa_uri,
    hash_password,
    hash_token,
    verify_mfa_code,
    verify_password,
)

settings = get_settings()


# ---------------------------------------------------------------------------
# Password hashing
# ---------------------------------------------------------------------------

def test_hash_and_verify_password():
    """Hashing then verifying the same password returns True; wrong one False."""
    hashed = hash_password("s3cret!")
    assert verify_password("s3cret!", hashed) is True
    assert verify_password("wrong-password", hashed) is False


# ---------------------------------------------------------------------------
# JWT access tokens
# ---------------------------------------------------------------------------

def test_create_and_decode_access_token():
    """Round-trip: create a token and decode it, payload should match."""
    token = create_access_token(user_id=1, role="admin")
    payload = decode_access_token(token)
    assert payload["sub"] == "1"
    assert payload["role"] == "admin"
    assert "exp" in payload


def test_expired_token_raises():
    """A token with a negative expiry must fail to decode."""
    expire = datetime.now(timezone.utc) - timedelta(seconds=10)
    token = jwt.encode(
        {"sub": "1", "role": "admin", "exp": expire},
        settings.JWT_SECRET_KEY,
        algorithm=settings.JWT_ALGORITHM,
    )
    with pytest.raises(JWTError):
        decode_access_token(token)


def test_invalid_token_raises():
    """Decoding a garbage string must raise JWTError."""
    with pytest.raises(JWTError):
        decode_access_token("not.a.valid.jwt.token")


# ---------------------------------------------------------------------------
# Refresh / hash tokens
# ---------------------------------------------------------------------------

def test_create_refresh_token_uniqueness():
    """Two refresh tokens should never collide."""
    t1 = create_refresh_token()
    t2 = create_refresh_token()
    assert t1 != t2
    assert len(t1) > 40  # url-safe base64 of 64 bytes


def test_hash_token():
    """hash_token must produce a consistent SHA-256 hex digest."""
    token = "test-token-value"
    expected = hashlib.sha256(token.encode()).hexdigest()
    assert hash_token(token) == expected
    # Deterministic: calling twice yields the same result
    assert hash_token(token) == hash_token(token)


# ---------------------------------------------------------------------------
# MFA / TOTP
# ---------------------------------------------------------------------------

def test_mfa_secret_and_verify():
    """Generate a secret, produce the current OTP, verify it succeeds."""
    secret = generate_mfa_secret()
    assert len(secret) > 0
    totp = pyotp.TOTP(secret)
    current_code = totp.now()
    assert verify_mfa_code(secret, current_code) is True


def test_mfa_wrong_code():
    """A clearly wrong code must be rejected."""
    secret = generate_mfa_secret()
    assert verify_mfa_code(secret, "000000") is False or verify_mfa_code(secret, "999999") is False


def test_mfa_uri_format():
    """The provisioning URI must start with the otpauth scheme."""
    secret = generate_mfa_secret()
    uri = get_mfa_uri(secret, "user@example.com")
    assert uri.startswith("otpauth://totp/")
    assert "user%40example.com" in uri or "user@example.com" in uri
