"""
Tests for typed response objects (GAMUserInfo and GAMTokenResponse).
"""


from datetime import datetime, timedelta
from pygeai_gam.types import GAMUserInfo, GAMTokenResponse


class TestGAMUserInfo:
    """Tests for GAMUserInfo dataclass."""
    
    def test_from_dict_with_all_fields(self):
        """Test creating GAMUserInfo from complete dict."""
        data = {
            "guid": "123e4567-e89b-12d3-a456-426614174000",
            "username": "john.doe",
            "email": "john.doe@example.com",
            "roles": ["admin", "user"],
            "extra_field": "extra_value"
        }
        
        user_info = GAMUserInfo.from_dict(data)
        
        assert user_info.guid == "123e4567-e89b-12d3-a456-426614174000"
        assert user_info.username == "john.doe"
        assert user_info.email == "john.doe@example.com"
        assert user_info.roles == ["admin", "user"]
        assert user_info.raw_data == data
        assert user_info.raw_data["extra_field"] == "extra_value"
    
    def test_from_dict_with_missing_optional_fields(self):
        """Test creating GAMUserInfo with missing optional fields."""
        data = {
            "guid": "abc123",
            "username": "jane.doe"
        }
        
        user_info = GAMUserInfo.from_dict(data)
        
        assert user_info.guid == "abc123"
        assert user_info.username == "jane.doe"
        assert user_info.email is None
        assert user_info.roles == []
        assert user_info.raw_data == data
    
    def test_from_dict_with_empty_roles(self):
        """Test creating GAMUserInfo with explicitly empty roles."""
        data = {
            "guid": "xyz789",
            "username": "test.user",
            "email": "test@example.com",
            "roles": []
        }
        
        user_info = GAMUserInfo.from_dict(data)
        
        assert user_info.roles == []
    
    def test_from_dict_with_missing_required_fields(self):
        """Test creating GAMUserInfo with missing required fields uses defaults."""
        data = {}
        
        user_info = GAMUserInfo.from_dict(data)
        
        assert user_info.guid == ""
        assert user_info.username == ""
        assert user_info.email is None
        assert user_info.roles == []


class TestGAMTokenResponse:
    """Tests for GAMTokenResponse dataclass."""
    
    def test_from_dict_with_all_fields(self):
        """Test creating GAMTokenResponse from complete dict."""
        user_data = {
            "guid": "user123",
            "username": "john.doe",
            "email": "john@example.com"
        }
        data = {
            "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
            "token_type": "Bearer",
            "expires_in": 3600,
            "refresh_token": "refresh_abc123",
            "scope": "gam_user_data gam_user_roles",
            "user_info": user_data
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert token_response.access_token == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
        assert token_response.token_type == "Bearer"
        assert token_response.expires_in == 3600
        assert token_response.refresh_token == "refresh_abc123"
        assert token_response.scope == "gam_user_data gam_user_roles"
        assert token_response.user_info is not None
        assert token_response.user_info.username == "john.doe"
        assert token_response.raw_data == data
    
    def test_from_dict_with_separate_user_info(self):
        """Test creating GAMTokenResponse with separate user_info parameter."""
        data = {
            "access_token": "token123",
            "token_type": "Bearer",
            "expires_in": 7200
        }
        user_data = {
            "guid": "user456",
            "username": "jane.doe"
        }
        
        token_response = GAMTokenResponse.from_dict(data, user_info=user_data)
        
        assert token_response.access_token == "token123"
        assert token_response.user_info is not None
        assert token_response.user_info.username == "jane.doe"
    
    def test_from_dict_without_user_info(self):
        """Test creating GAMTokenResponse without user info."""
        data = {
            "access_token": "token_abc",
            "expires_in": 1800
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert token_response.access_token == "token_abc"
        assert token_response.token_type == "Bearer"  # default
        assert token_response.expires_in == 1800
        assert token_response.refresh_token is None
        assert token_response.scope is None
        assert token_response.user_info is None
    
    def test_from_dict_with_minimal_data(self):
        """Test creating GAMTokenResponse with only required field."""
        data = {"access_token": "minimal_token"}
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert token_response.access_token == "minimal_token"
        assert token_response.token_type == "Bearer"
        assert token_response.expires_in is None
        assert token_response.refresh_token is None
        assert token_response.scope is None
        assert token_response.user_info is None
    
    def test_get_expiry_time_with_expires_in(self):
        """Test calculating expiry time when expires_in is set."""
        data = {
            "access_token": "token123",
            "expires_in": 3600
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        before = datetime.now()
        expiry = token_response.get_expiry_time()

        
        assert expiry is not None
        expected_expiry = before + timedelta(seconds=3600)
        # Allow 1 second tolerance for test execution time
        assert abs((expiry - expected_expiry).total_seconds()) < 1
    
    def test_get_expiry_time_without_expires_in(self):
        """Test get_expiry_time returns None when expires_in not set."""
        data = {"access_token": "token123"}
        
        token_response = GAMTokenResponse.from_dict(data)
        expiry = token_response.get_expiry_time()
        
        assert expiry is None
    
    def test_is_expired_with_valid_token(self):
        """Test is_expired returns False for valid token."""
        data = {
            "access_token": "token123",
            "expires_in": 3600  # 1 hour from now
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert not token_response.is_expired()
        assert not token_response.is_expired(buffer_seconds=60)
    
    def test_is_expired_with_expired_token(self):
        """Test is_expired returns True for expired token."""
        data = {
            "access_token": "token123",
            "expires_in": 0  # Already expired
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert token_response.is_expired()
    
    def test_is_expired_with_buffer(self):
        """Test is_expired with buffer_seconds parameter."""
        data = {
            "access_token": "token123",
            "expires_in": 120  # 2 minutes from now
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        
        # With 60 second buffer, should not be expired
        assert not token_response.is_expired(buffer_seconds=60)
        
        # With 180 second buffer (3 minutes), should be expired
        assert token_response.is_expired(buffer_seconds=180)
    
    def test_is_expired_without_expires_in(self):
        """Test is_expired returns True when expires_in not set."""
        data = {"access_token": "token123"}
        
        token_response = GAMTokenResponse.from_dict(data)
        
        assert token_response.is_expired()
    
    def test_to_dict_with_all_fields(self):
        """Test converting GAMTokenResponse back to dict."""
        user_data = {
            "guid": "user123",
            "username": "john.doe",
            "email": "john@example.com"
        }
        data = {
            "access_token": "token123",
            "token_type": "Bearer",
            "expires_in": 3600,
            "refresh_token": "refresh123",
            "scope": "gam_user_data",
            "user_info": user_data
        }
        
        token_response = GAMTokenResponse.from_dict(data)
        result = token_response.to_dict()
        
        assert result["access_token"] == "token123"
        assert result["token_type"] == "Bearer"
        assert result["expires_in"] == 3600
        assert result["refresh_token"] == "refresh123"
        assert result["scope"] == "gam_user_data"
        assert result["user_info"]["username"] == "john.doe"
    
    def test_to_dict_with_minimal_fields(self):
        """Test converting minimal GAMTokenResponse to dict."""
        data = {"access_token": "token123"}
        
        token_response = GAMTokenResponse.from_dict(data)
        result = token_response.to_dict()
        
        assert result["access_token"] == "token123"
        assert result["token_type"] == "Bearer"
        assert "expires_in" not in result
        assert "refresh_token" not in result
        assert "scope" not in result
        assert "user_info" not in result
    
    def test_to_dict_round_trip(self):
        """Test that to_dict() output can be used to create new instance."""
        original_data = {
            "access_token": "token123",
            "token_type": "Custom",
            "expires_in": 7200,
            "refresh_token": "refresh123",
            "scope": "custom_scope"
        }
        
        token1 = GAMTokenResponse.from_dict(original_data)
        dict_output = token1.to_dict()
        token2 = GAMTokenResponse.from_dict(dict_output)
        
        assert token1.access_token == token2.access_token
        assert token1.token_type == token2.token_type
        assert token1.expires_in == token2.expires_in
        assert token1.refresh_token == token2.refresh_token
        assert token1.scope == token2.scope
