"""
Unit tests for security utilities (password hashing and JWT tokens).
"""

import datetime

import jwt
import pytest

from app.config import settings
from app.core.exceptions import UnauthorizedException
from app.core.security import (
    create_access_token,
    create_refresh_token,
    decode_access_token,
    decode_refresh_token,
    decode_token,
    hash_password,
    verify_password,
)


class TestPasswordHashing:
    """Tests for bcrypt password hashing and verification."""

    def test_hash_and_verify_success(self) -> None:
        raw_password = "SecurePassword123!"
        hashed = hash_password(raw_password)

        assert hashed != raw_password
        assert hashed.startswith("$2")
        assert verify_password(raw_password, hashed) is True

    def test_verify_wrong_password_returns_false(self) -> None:
        hashed = hash_password("CorrectPassword")
        assert verify_password("WrongPassword", hashed) is False

    def test_unique_salts(self) -> None:
        pwd = "SamePassword"
        hash1 = hash_password(pwd)
        hash2 = hash_password(pwd)
        assert hash1 != hash2
        assert verify_password(pwd, hash1) is True
        assert verify_password(pwd, hash2) is True


class TestJWTTokens:
    """Tests for access/refresh token generation and decoding."""

    def test_create_and_decode_access_token(self) -> None:
        subject = "user-uuid-12345"
        token = create_access_token(subject=subject, extra_claims={"role": "admin", "lab_id": "lab-1"})

        payload = decode_access_token(token)
        assert payload["sub"] == subject
        assert payload["type"] == "access"
        assert payload["role"] == "admin"
        assert payload["lab_id"] == "lab-1"
        assert "exp" in payload

    def test_create_and_decode_refresh_token(self) -> None:
        subject = "user-uuid-67890"
        token = create_refresh_token(subject=subject)

        payload = decode_refresh_token(token)
        assert payload["sub"] == subject
        assert payload["type"] == "refresh"
        assert "exp" in payload

    def test_decode_access_token_with_refresh_token_raises_unauthorized(self) -> None:
        token = create_refresh_token(subject="user-uuid-123")
        with pytest.raises(UnauthorizedException) as exc_info:
            decode_access_token(token)
        assert exc_info.value.code == "UNAUTHORIZED"

    def test_decode_refresh_token_with_access_token_raises_unauthorized(self) -> None:
        token = create_access_token(subject="user-uuid-123")
        with pytest.raises(UnauthorizedException) as exc_info:
            decode_refresh_token(token)
        assert exc_info.value.code == "UNAUTHORIZED"

    def test_invalid_token_signature_raises_unauthorized(self) -> None:
        expiry = datetime.datetime.now(datetime.UTC) + datetime.timedelta(hours=1)
        invalid_token = jwt.encode(
            {"sub": "fake", "type": "access", "exp": expiry},
            "wrong-secret-key-of-at-least-32-characters",
            algorithm="HS256",
        )
        with pytest.raises(UnauthorizedException) as exc_info:
            decode_token(invalid_token)
        assert exc_info.value.code == "UNAUTHORIZED"

    def test_expired_token_raises_unauthorized(self) -> None:
        expired_payload = {
            "sub": "user-uuid",
            "type": "access",
            "exp": datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=10),
        }
        expired_token = jwt.encode(expired_payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
        with pytest.raises(UnauthorizedException) as exc_info:
            decode_token(expired_token)
        assert exc_info.value.code == "UNAUTHORIZED"


def test_tokens_are_unique_even_when_issued_in_the_same_second() -> None:
    assert create_access_token("same-user") != create_access_token("same-user")
    assert create_refresh_token("same-user") != create_refresh_token("same-user")
