"""
Tests für den Consent Client
"""

import pytest
import jwt
from datetime import datetime, timedelta
from unittest.mock import AsyncMock, patch, MagicMock
import sys
import os

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from consent_client import (
    generate_jwt_token,
    generate_demo_token,
    DocumentType,
    ConsentStatus,
    DocumentVersion,
    ConsentClient,
    JWT_SECRET,
)


class TestJWTTokenGeneration:
    """Tests für JWT Token Generierung"""

    def test_generate_jwt_token_default(self):
        """Test JWT generation with default values"""
        token = generate_jwt_token()

        assert token is not None
        assert isinstance(token, str)
        assert len(token) > 0

        # Decode and verify
        decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
        assert "user_id" in decoded
        assert decoded["email"] == "demo@breakpilot.app"
        assert decoded["role"] == "user"
        assert "exp" in decoded
        assert "iat" in decoded

    def test_generate_jwt_token_custom_values(self):
        """Test JWT generation with custom values"""
        user_id = "test-user-123"
        email = "test@example.com"
        role = "admin"

        token = generate_jwt_token(
            user_id=user_id,
            email=email,
            role=role,
            expires_hours=48
        )

        decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
        assert decoded["user_id"] == user_id
        assert decoded["email"] == email
        assert decoded["role"] == role

    def test_generate_jwt_token_expiration(self):
        """Test that token expiration is set correctly"""
        token = generate_jwt_token(expires_hours=1)
        decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])

        exp = datetime.utcfromtimestamp(decoded["exp"])
        now = datetime.utcnow()

        # Should expire in approximately 1 hour
        time_diff = exp - now
        assert time_diff.total_seconds() > 3500  # At least 58 minutes
        assert time_diff.total_seconds() < 3700  # At most 62 minutes

    def test_generate_demo_token(self):
        """Test demo token generation"""
        token = generate_demo_token()

        decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
        assert decoded["user_id"].startswith("demo-user-")
        assert decoded["email"] == "demo@breakpilot.app"
        assert decoded["role"] == "user"

    def test_tokens_are_unique(self):
        """Test that generated tokens are unique"""
        tokens = [generate_demo_token() for _ in range(10)]
        assert len(set(tokens)) == 10  # All tokens should be unique

    def test_jwt_token_signature(self):
        """Test that token signature is valid"""
        token = generate_jwt_token()

        # Should not raise exception
        jwt.decode(token, JWT_SECRET, algorithms=["HS256"])

        # Should raise exception with wrong secret
        with pytest.raises(jwt.InvalidSignatureError):
            jwt.decode(token, "wrong-secret", algorithms=["HS256"])


class TestDocumentType:
    """Tests für DocumentType Enum"""

    def test_document_types(self):
        """Test all document types exist"""
        assert DocumentType.TERMS.value == "terms"
        assert DocumentType.PRIVACY.value == "privacy"
        assert DocumentType.COOKIES.value == "cookies"
        assert DocumentType.COMMUNITY.value == "community"

    def test_document_type_is_string(self):
        """Test that document types can be used as strings"""
        assert str(DocumentType.TERMS) == "DocumentType.TERMS"
        assert DocumentType.TERMS.value == "terms"


class TestConsentStatus:
    """Tests für ConsentStatus Dataclass"""

    def test_consent_status_basic(self):
        """Test basic ConsentStatus creation"""
        status = ConsentStatus(has_consent=True)

        assert status.has_consent is True
        assert status.current_version_id is None
        assert status.consented_version is None
        assert status.needs_update is False
        assert status.consented_at is None

    def test_consent_status_full(self):
        """Test ConsentStatus with all fields"""
        status = ConsentStatus(
            has_consent=True,
            current_version_id="version-123",
            consented_version="1.0.0",
            needs_update=False,
            consented_at="2024-01-01T00:00:00Z"
        )

        assert status.has_consent is True
        assert status.current_version_id == "version-123"
        assert status.consented_version == "1.0.0"
        assert status.needs_update is False
        assert status.consented_at == "2024-01-01T00:00:00Z"


class TestDocumentVersion:
    """Tests für DocumentVersion Dataclass"""

    def test_document_version_creation(self):
        """Test DocumentVersion creation"""
        version = DocumentVersion(
            id="doc-version-123",
            document_id="doc-123",
            version="1.0.0",
            language="de",
            title="Test Document",
            content="<p>Test content</p>",
            summary="Test summary"
        )

        assert version.id == "doc-version-123"
        assert version.document_id == "doc-123"
        assert version.version == "1.0.0"
        assert version.language == "de"
        assert version.title == "Test Document"
        assert version.content == "<p>Test content</p>"
        assert version.summary == "Test summary"


class TestConsentClient:
    """Tests für ConsentClient"""

    def test_client_initialization(self):
        """Test client initialization"""
        client = ConsentClient()
        # In Docker: consent-service:8081, locally: localhost:8081
        assert client.base_url in ("http://localhost:8081", "http://consent-service:8081")
        assert "/api/v1" in client.api_url

    def test_client_custom_url(self):
        """Test client with custom URL"""
        client = ConsentClient(base_url="https://custom.example.com/")
        assert client.base_url == "https://custom.example.com"
        assert client.api_url == "https://custom.example.com/api/v1"

    def test_get_headers(self):
        """Test header generation"""
        client = ConsentClient()
        token = "test-token-123"

        headers = client._get_headers(token)

        assert headers["Authorization"] == "Bearer test-token-123"
        assert headers["Content-Type"] == "application/json"


class TestConsentClientAsync:
    """Async tests für ConsentClient"""

    @pytest.mark.asyncio
    async def test_check_consent_success(self):
        """Test successful consent check"""
        client = ConsentClient()

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "has_consent": True,
            "current_version_id": "version-123",
            "consented_version": "1.0.0",
            "needs_update": False,
            "consented_at": "2024-01-01T00:00:00Z"
        }

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            status = await client.check_consent(
                jwt_token="test-token",
                document_type=DocumentType.TERMS
            )

            assert status.has_consent is True
            assert status.current_version_id == "version-123"

    @pytest.mark.asyncio
    async def test_check_consent_not_found(self):
        """Test consent check when user has no consent"""
        client = ConsentClient()

        mock_response = MagicMock()
        mock_response.status_code = 404

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            status = await client.check_consent(
                jwt_token="test-token",
                document_type=DocumentType.TERMS
            )

            assert status.has_consent is False
            assert status.needs_update is True

    @pytest.mark.asyncio
    async def test_check_consent_connection_error(self):
        """Test consent check when service is unavailable"""
        import httpx

        client = ConsentClient()

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.RequestError("Connection error")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            status = await client.check_consent(
                jwt_token="test-token",
                document_type=DocumentType.TERMS
            )

            # Should not block user when service is unavailable
            assert status.has_consent is True
            assert status.needs_update is False

    @pytest.mark.asyncio
    async def test_health_check_success(self):
        """Test successful health check"""
        client = ConsentClient()

        mock_response = MagicMock()
        mock_response.status_code = 200

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            is_healthy = await client.health_check()
            assert is_healthy is True

    @pytest.mark.asyncio
    async def test_health_check_failure(self):
        """Test failed health check"""
        import httpx

        client = ConsentClient()

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.RequestError("Connection refused")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            is_healthy = await client.health_check()
            assert is_healthy is False

    @pytest.mark.asyncio
    async def test_give_consent_success(self):
        """Test successful consent submission"""
        client = ConsentClient()

        mock_response = MagicMock()
        mock_response.status_code = 201

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            success = await client.give_consent(
                jwt_token="test-token",
                document_type="terms",
                version_id="version-123",
                consented=True
            )

            assert success is True

    @pytest.mark.asyncio
    async def test_give_consent_failure(self):
        """Test failed consent submission"""
        client = ConsentClient()

        mock_response = MagicMock()
        mock_response.status_code = 400

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            success = await client.give_consent(
                jwt_token="test-token",
                document_type="terms",
                version_id="version-123",
                consented=True
            )

            assert success is False


class TestValidation:
    """Tests für Validierungslogik"""

    def test_valid_document_types(self):
        """Test that only valid document types are accepted"""
        valid_types = ["terms", "privacy", "cookies", "community"]

        for doc_type in DocumentType:
            assert doc_type.value in valid_types

    def test_jwt_expiration_validation(self):
        """Test that expired tokens are rejected"""
        # Create token that expired 1 hour ago
        expired_payload = {
            "user_id": "test-user",
            "email": "test@example.com",
            "role": "user",
            "exp": datetime.utcnow() - timedelta(hours=1),
            "iat": datetime.utcnow() - timedelta(hours=2),
        }

        expired_token = jwt.encode(expired_payload, JWT_SECRET, algorithm="HS256")

        with pytest.raises(jwt.ExpiredSignatureError):
            jwt.decode(expired_token, JWT_SECRET, algorithms=["HS256"])


# Performance Tests
class TestPerformance:
    """Performance tests"""

    def test_token_generation_performance(self):
        """Test that token generation is fast"""
        import time

        start = time.time()
        for _ in range(100):
            generate_jwt_token()
        elapsed = time.time() - start

        # Should generate 100 tokens in less than 1 second
        assert elapsed < 1.0, f"Token generation too slow: {elapsed}s for 100 tokens"


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