"""
Unit tests for authentication system (app/auth.py)
Tests JWT tokens, user management, and password hashing with SQLModel support
"""
import pytest
from datetime import datetime, timedelta
from unittest.mock import patch
from app.auth import UserManager, create_access_token, verify_token, get_user_from_token
from app.models.auth import UserTable


class TestUserManager:
    """Tests for UserManager class using SQLModel"""

    def test_create_user_success(self, user_manager):
        """Test successful user creation"""
        user = user_manager.create_user("testuser", "password123")
        assert user.username == "testuser"
        assert hasattr(user, "hashed_password")
        assert user.created_at is not None
        assert user.last_login is None
        
        # Verify it's in the database
        db_user = user_manager.get_user("testuser")
        assert db_user is not None
        assert db_user.username == "testuser"

    def test_create_user_hashing(self, user_manager):
        """Test that passwords are properly hashed with bcrypt"""
        user = user_manager.create_user("testuser", "password123")
        # Hash should not be the plain password
        assert user.hashed_password != "password123"
        # Bcrypt hashes start with $2b$
        assert user.hashed_password.startswith("$2b$")
        # Hash should be 60 characters (bcrypt standard)
        assert len(user.hashed_password) == 60

    def test_create_user_duplicate(self, user_manager):
        """Test that duplicate usernames are rejected"""
        user_manager.create_user("testuser", "password123")
        with pytest.raises(ValueError, match="already exists"):
            user_manager.create_user("testuser", "different456")

    def test_create_user_password_truncation(self, user_manager):
        """Test that passwords longer than 72 bytes are truncated (bcrypt limit)"""
        long_password = "a" * 100
        user = user_manager.create_user("testuser", long_password)
        assert user.username == "testuser"

    def test_authenticate_user_success(self, user_manager):
        """Test successful user authentication"""
        user_manager.create_user("testuser", "password123")
        user = user_manager.authenticate_user("testuser", "password123")
        assert user is not None
        assert user.username == "testuser"
        assert user.last_login is not None

    def test_authenticate_user_wrong_password(self, user_manager):
        """Test authentication with wrong password"""
        user_manager.create_user("testuser", "password123")
        user = user_manager.authenticate_user("testuser", "wrongpassword")
        assert user is None

    def test_authenticate_user_nonexistent(self, user_manager):
        """Test authentication with non-existent user"""
        user = user_manager.authenticate_user("nonexistent", "password")
        assert user is None

    def test_authenticate_updates_last_login(self, user_manager):
        """Test that authentication updates last_login timestamp"""
        user_manager.create_user("testuser", "password123")
        user_before = user_manager.get_user("testuser")
        assert user_before.last_login is None

        user_manager.authenticate_user("testuser", "password123")
        user_after = user_manager.get_user("testuser")
        assert user_after.last_login is not None

    def test_get_user(self, user_manager):
        """Test getting a user by username"""
        user_manager.create_user("testuser", "password123")
        user = user_manager.get_user("testuser")
        assert user is not None
        assert user.username == "testuser"

    def test_get_user_by_id(self, user_manager):
        """Test getting a user by ID"""
        user = user_manager.create_user("testuser", "password123")
        user_id = user.id
        db_user = user_manager.get_user_by_id(user_id)
        assert db_user is not None
        assert db_user.username == "testuser"

    def test_get_user_nonexistent(self, user_manager):
        """Test getting a non-existent user"""
        user = user_manager.get_user("nonexistent")
        assert user is None

    def test_update_user(self, user_manager):
        """Test updating user information"""
        user = user_manager.create_user("testuser", "password123")
        updated = user_manager.update_user(user.id, {"full_name": "New Name", "email": "new@example.com"})
        assert updated.full_name == "New Name"
        assert updated.email == "new@example.com"
        
        db_user = user_manager.get_user("testuser")
        assert db_user.full_name == "New Name"


class TestJWTToken:
    """Tests for JWT token functions"""

    def test_create_access_token(self):
        """Test creating an access token"""
        token = create_access_token({"sub": "testuser"})
        assert isinstance(token, str)
        assert len(token) > 0

    def test_verify_token_success(self):
        """Test verifying a valid token"""
        token = create_access_token({"sub": "testuser"})
        username = verify_token(token)
        assert username == "testuser"

    @pytest.mark.skip(reason="Problematic mock with datetime and jose library")
    def test_verify_token_expired(self):
        """Test verifying an expired token"""
        with patch('app.auth.datetime') as mock_datetime:
            # Set fixed time
            now = datetime.utcnow()
            mock_datetime.utcnow.return_value = now
            
            # Create token that expires in 1 minute
            token = create_access_token({"sub": "testuser"}, expires_delta=timedelta(minutes=1))
            
            # Move time forward by 2 minutes
            mock_datetime.utcnow.return_value = now + timedelta(minutes=2)
            
            username = verify_token(token)
            assert username is None

    def test_verify_token_invalid(self):
        """Test verifying an invalid token"""
        username = verify_token("invalid-token")
        assert username is None

    def test_get_user_from_token(self):
        """Test get_user_from_token alias"""
        token = create_access_token({"sub": "testuser"})
        username = get_user_from_token(token)
        assert username == "testuser"
