"""
Tests for authentication module.
"""

import time
from unittest.mock import MagicMock, Mock, patch

import pytest
import requests

from getbiji_box.auth import AuthAPI, AuthManager
from getbiji_box.exceptions import APIError, AuthenticationError, TokenExpiredError
from getbiji_box.models import TokenInfo


class TestAuthAPI:
    """Test cases for AuthAPI class."""

    def test_init(self):
        """Test AuthAPI initialization."""
        auth = AuthAPI(
            token="test_token",
            refresh_token="test_refresh_token",
            token_expire_at=int(time.time()) + 3600,
            refresh_token_expire_at=int(time.time()) + 7200,
        )
        assert auth.token == "test_token"
        assert auth.refresh_token == "test_refresh_token"

    def test_is_token_expired_true(self):
        """Test token expiration check returns True."""
        auth = AuthAPI(
            token="test_token",
            token_expire_at=int(time.time()) - 100,  # Expired
        )
        assert auth.is_token_expired() is True

    def test_is_token_expired_false(self):
        """Test token expiration check returns False."""
        auth = AuthAPI(
            token="test_token",
            token_expire_at=int(time.time()) + 3600,  # Not expired
        )
        assert auth.is_token_expired() is False

    def test_is_token_expired_with_buffer(self):
        """Test token expiration with buffer time."""
        auth = AuthAPI(
            token="test_token",
            token_expire_at=int(time.time()) + 200,  # Within buffer
        )
        assert auth.is_token_expired(buffer_seconds=300) is True

    def test_update_tokens(self):
        """Test updating tokens."""
        auth = AuthAPI(token="old_token")
        token_info = TokenInfo(
            token="new_token",
            refresh_token="new_refresh_token",
            token_expire_at=int(time.time()) + 3600,
            refresh_token_expire_at=int(time.time()) + 7200,
        )

        auth.update_tokens(token_info)
        assert auth.token == "new_token"
        assert auth.refresh_token == "new_refresh_token"

    def test_refresh_token_success(self):
        """Test successful token refresh."""
        mock_response = Mock()
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "success": True,
                "token": {
                    "token": "new_token",
                    "refresh_token": "new_refresh_token",
                    "token_expire_at": int(time.time()) + 3600,
                    "refresh_token_expire_at": int(time.time()) + 7200,
                },
            },
        }

        mock_session = MagicMock(spec=requests.Session)
        mock_session.post.return_value = mock_response
        mock_session.headers = {}

        auth = AuthAPI(
            token="old_token",
            refresh_token="refresh_token",
            session=mock_session,
        )

        token_info = auth.refresh()

        assert token_info.token == "new_token"
        assert auth.token == "new_token"

    def test_refresh_token_api_error(self):
        """Test token refresh with API error."""
        mock_response = Mock()
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "h": {"c": 1, "e": "Invalid refresh token"},
        }

        mock_session = MagicMock(spec=requests.Session)
        mock_session.post.return_value = mock_response
        mock_session.headers = {}

        auth = AuthAPI(
            token="old_token",
            refresh_token="invalid_refresh_token",
            session=mock_session,
        )

        with pytest.raises(APIError) as exc_info:
            auth.refresh()

        assert exc_info.value.code == 1

    def test_refresh_token_no_refresh_token(self):
        """Test refresh without refresh token raises error."""
        auth = AuthAPI(token="test_token")

        with pytest.raises(AuthenticationError) as exc_info:
            auth.refresh()

        assert "No refresh token" in str(exc_info.value)

    def test_ensure_token_valid_when_valid(self):
        """Test ensure_token_valid does nothing when token is valid."""
        auth = AuthAPI(
            token="test_token",
            token_expire_at=int(time.time()) + 3600,
        )

        # Should not raise any exception
        auth.ensure_token_valid()

    def test_ensure_token_valid_when_expired(self):
        """Test ensure_token_valid refreshes when token is expired."""
        mock_response = Mock()
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "success": True,
                "token": {
                    "token": "new_token",
                    "refresh_token": "new_refresh_token",
                    "token_expire_at": int(time.time()) + 3600,
                    "refresh_token_expire_at": int(time.time()) + 7200,
                },
            },
        }

        mock_session = MagicMock(spec=requests.Session)
        mock_session.post.return_value = mock_response
        mock_session.headers = {}

        auth = AuthAPI(
            token="expired_token",
            refresh_token="refresh_token",
            token_expire_at=int(time.time()) - 100,
            session=mock_session,
        )

        auth.ensure_token_valid()
        assert auth.token == "new_token"


class TestAuthManager:
    """Test cases for AuthManager class."""

    def test_init(self):
        """Test AuthManager initialization."""
        manager = AuthManager(
            token="test_token",
            refresh_token="refresh_token",
        )
        assert manager.token == "test_token"

    def test_session_property(self):
        """Test getting session from manager."""
        manager = AuthManager(token="test_token")
        session = manager.session
        assert session is not None
        assert "Authorization" in session.headers

    @patch.object(AuthAPI, "ensure_token_valid")
    def test_ensure_valid(self, mock_ensure):
        """Test ensure_valid calls underlying AuthAPI."""
        manager = AuthManager(token="test_token")
        manager.ensure_valid()
        mock_ensure.assert_called_once()

    @patch.object(AuthAPI, "refresh")
    def test_refresh(self, mock_refresh):
        """Test refresh calls underlying AuthAPI."""
        mock_refresh.return_value = TokenInfo(
            token="new",
            refresh_token="new_refresh",
            token_expire_at=123,
            refresh_token_expire_at=456,
        )
        manager = AuthManager(token="test_token")
        result = manager.refresh()
        mock_refresh.assert_called_once()
        assert result.token == "new"

    def test_auth_api_property(self):
        """Test auth_api property."""
        manager = AuthManager(token="test_token")
        assert manager.auth_api is not None
        assert manager.auth_api.token == "test_token"


class TestAuthAPIAdditional:
    """Additional test cases for AuthAPI coverage."""

    def test_token_expire_at_property(self):
        """Test token_expire_at property getter."""
        auth = AuthAPI(token="t", token_expire_at=12345)
        assert auth.token_expire_at == 12345

    def test_refresh_token_expire_at_property(self):
        """Test refresh_token_expire_at property getter."""
        auth = AuthAPI(token="t", refresh_token_expire_at=67890)
        assert auth.refresh_token_expire_at == 67890

    def test_is_refresh_token_expired_true(self):
        """Test refresh token expiration returns True."""
        auth = AuthAPI(
            token="t",
            refresh_token="rt",
            refresh_token_expire_at=int(time.time()) - 100,
        )
        assert auth.is_refresh_token_expired() is True

    def test_is_refresh_token_expired_false(self):
        """Test refresh token expiration returns False."""
        auth = AuthAPI(
            token="t",
            refresh_token="rt",
            refresh_token_expire_at=int(time.time()) + 3600,
        )
        assert auth.is_refresh_token_expired() is False

    def test_is_refresh_token_expired_zero(self):
        """Test refresh token with 0 expire_at."""
        auth = AuthAPI(token="t", refresh_token="rt", refresh_token_expire_at=0)
        assert auth.is_refresh_token_expired() is False

    def test_parse_jwt_expiry(self):
        """Test JWT expiry parsing."""
        import base64
        import json

        payload = base64.b64encode(json.dumps({"exp": 9999999}).encode()).decode()
        payload = payload.rstrip("=")
        token = f"header.{payload}.signature"

        auth = AuthAPI(token="t")
        result = auth._parse_jwt_expiry(token)
        assert result == 9999999

    def test_parse_jwt_expiry_invalid(self):
        """Test JWT expiry parsing with invalid token."""
        auth = AuthAPI(token="t")
        assert auth._parse_jwt_expiry("invalid") == 0

    def test_parse_jwt_expiry_bad_base64(self):
        """Test JWT expiry parsing with bad base64."""
        auth = AuthAPI(token="t")
        assert auth._parse_jwt_expiry("a.!!!.b") == 0

    def test_refresh_token_expired_raises(self):
        """Test refresh when refresh token is expired."""
        auth = AuthAPI(
            token="t",
            refresh_token="rt",
            refresh_token_expire_at=int(time.time()) - 100,
        )
        with pytest.raises(TokenExpiredError):
            auth.refresh()

    def test_refresh_not_successful(self):
        """Test refresh when API returns success=False."""
        mock_response = Mock()
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "h": {"c": 0},
            "c": {"success": False},
        }

        mock_session = MagicMock(spec=requests.Session)
        mock_session.post.return_value = mock_response
        mock_session.headers = {}

        auth = AuthAPI(token="t", refresh_token="rt", session=mock_session)
        with pytest.raises(AuthenticationError, match="not successful"):
            auth.refresh()

    def test_refresh_request_exception(self):
        """Test refresh when network error occurs."""
        mock_session = MagicMock(spec=requests.Session)
        mock_session.post.side_effect = requests.RequestException("Connection failed")
        mock_session.headers = {}

        auth = AuthAPI(token="t", refresh_token="rt", session=mock_session)
        with pytest.raises(AuthenticationError, match="Failed to refresh"):
            auth.refresh()

    def test_set_session(self):
        """Test set_session method."""
        auth = AuthAPI(token="my_token")
        new_session = requests.Session()
        auth.set_session(new_session)
        assert auth.get_session() is new_session
        assert "Bearer my_token" in new_session.headers.get("Authorization", "")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
