"""
CRITICAL SECURITY TESTS for API Token plaintext storage.

These tests verify that live/production tokens can NEVER have their
plaintext keys stored in the database, only sandbox/test tokens.
"""
from django.test import TestCase
from django.core.exceptions import ValidationError
from core.models import Tenant
from smoothschedule.users.models import User
from smoothschedule.public_api.models import APIToken


class APITokenPlaintextSecurityTests(TestCase):
    """
    Test suite to verify that plaintext tokens are NEVER stored for live tokens.

    SECURITY CRITICAL: These tests ensure that production API tokens cannot
    accidentally leak by being stored in plaintext.
    """

    def setUp(self):
        """Set up test tenant and user."""
        # Create a test tenant
        self.tenant = Tenant.objects.create(
            schema_name='test_security',
            name='Test Security Tenant'
        )

        # Create domain for the tenant
        from core.models import Domain
        self.domain = Domain.objects.create(
            domain='test-security.localhost',
            tenant=self.tenant,
            is_primary=True
        )

        # Create a test user
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123',
            tenant=self.tenant
        )

    def test_sandbox_token_can_store_plaintext(self):
        """
        Sandbox tokens SHOULD be allowed to store plaintext keys.
        This is safe because they only work with test data.
        """
        # Generate a sandbox token
        full_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=True)

        # Verify it's a test token
        self.assertTrue(full_key.startswith('ss_test_'))

        # Create token with plaintext - should succeed
        token = APIToken.objects.create(
            tenant=self.tenant,
            name='Test Sandbox Token',
            key_hash=key_hash,
            key_prefix=key_prefix,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=True,
            plaintext_key=full_key  # ALLOWED for sandbox tokens
        )

        # Verify it was saved
        self.assertIsNotNone(token.id)
        self.assertEqual(token.plaintext_key, full_key)
        self.assertTrue(token.is_sandbox)

    def test_live_token_cannot_store_plaintext(self):
        """
        SECURITY TEST: Live tokens must NEVER store plaintext keys.
        This test verifies the model validation prevents this.
        """
        # Generate a live token
        full_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=False)

        # Verify it's a live token
        self.assertTrue(full_key.startswith('ss_live_'))

        # Try to create token with plaintext - should FAIL
        with self.assertRaises(ValidationError) as context:
            token = APIToken(
                tenant=self.tenant,
                name='Test Live Token',
                key_hash=key_hash,
                key_prefix=key_prefix,
                scopes=['services:read'],
                created_by=self.user,
                is_sandbox=False,
                plaintext_key=full_key  # NOT ALLOWED for live tokens
            )
            token.save()  # This should raise ValidationError

        # Verify the error message mentions security violation
        error_dict = context.exception.message_dict
        self.assertIn('plaintext_key', error_dict)
        self.assertIn('SECURITY VIOLATION', str(error_dict['plaintext_key'][0]))
        self.assertIn('live/production tokens', str(error_dict['plaintext_key'][0]))

    def test_cannot_store_ss_live_in_plaintext(self):
        """
        SECURITY TEST: Even for sandbox tokens, we should never accept
        a plaintext key that starts with ss_live_*.

        This is a belt-and-suspenders check to catch bugs in token generation.
        """
        # Generate a live token (to get the ss_live_* format)
        live_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=False)

        # Try to create a token marked as sandbox but with a live key plaintext
        with self.assertRaises(ValidationError) as context:
            token = APIToken(
                tenant=self.tenant,
                name='Malicious Token',
                key_hash=key_hash,
                key_prefix=key_prefix,
                scopes=['services:read'],
                created_by=self.user,
                is_sandbox=True,  # Marked as sandbox
                plaintext_key=live_key  # But trying to store ss_live_* plaintext
            )
            token.save()  # This should raise ValidationError

        # Verify the error mentions ss_live_
        error_dict = context.exception.message_dict
        self.assertIn('plaintext_key', error_dict)
        self.assertIn('ss_live_', str(error_dict['plaintext_key'][0]))
        self.assertIn('SECURITY VIOLATION', str(error_dict['plaintext_key'][0]))

    def test_plaintext_must_start_with_ss_test(self):
        """
        SECURITY TEST: Any plaintext key must start with ss_test_*.
        Invalid formats should be rejected.
        """
        _, key_hash, key_prefix = APIToken.generate_key(is_sandbox=True)

        # Try with an invalid plaintext format
        with self.assertRaises(ValidationError) as context:
            token = APIToken(
                tenant=self.tenant,
                name='Invalid Token',
                key_hash=key_hash,
                key_prefix=key_prefix,
                scopes=['services:read'],
                created_by=self.user,
                is_sandbox=True,
                plaintext_key='invalid_format_123456789'  # Wrong format
            )
            token.save()

        # Verify the error
        error_dict = context.exception.message_dict
        self.assertIn('plaintext_key', error_dict)
        self.assertIn('ss_test_', str(error_dict['plaintext_key'][0]))

    def test_live_token_without_plaintext_succeeds(self):
        """
        Live tokens WITHOUT plaintext should save successfully.
        This is the normal, secure operation.
        """
        # Generate a live token
        full_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=False)

        # Create token WITHOUT plaintext - should succeed
        token = APIToken.objects.create(
            tenant=self.tenant,
            name='Normal Live Token',
            key_hash=key_hash,
            key_prefix=key_prefix,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=False,
            plaintext_key=None  # Correct: no plaintext for live tokens
        )

        # Verify it was saved
        self.assertIsNotNone(token.id)
        self.assertIsNone(token.plaintext_key)
        self.assertFalse(token.is_sandbox)

    def test_updating_live_token_to_add_plaintext_fails(self):
        """
        SECURITY TEST: Even updating an existing live token to add
        plaintext should fail.
        """
        # Create a live token without plaintext (normal case)
        full_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=False)
        token = APIToken.objects.create(
            tenant=self.tenant,
            name='Live Token',
            key_hash=key_hash,
            key_prefix=key_prefix,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=False,
            plaintext_key=None
        )

        # Try to update it to add plaintext
        with self.assertRaises(ValidationError):
            token.plaintext_key = full_key  # Try to add plaintext
            token.save()  # Should fail

    def test_sandbox_token_plaintext_matches_hash(self):
        """
        Verify that for sandbox tokens, the plaintext key when hashed
        matches the stored key_hash.
        """
        # Generate a sandbox token
        full_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=True)

        # Create token with plaintext
        token = APIToken.objects.create(
            tenant=self.tenant,
            name='Test Token',
            key_hash=key_hash,
            key_prefix=key_prefix,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=True,
            plaintext_key=full_key
        )

        # Verify the plaintext hashes to the same value
        computed_hash = APIToken.hash_key(token.plaintext_key)
        self.assertEqual(computed_hash, token.key_hash)

    def test_bulk_create_cannot_bypass_validation(self):
        """
        SECURITY TEST: Ensure bulk_create doesn't bypass validation.
        Note: Django's bulk_create doesn't call save(), so we need to be careful.
        """
        # For now, document that bulk_create should not be used for APITokens
        # or should be wrapped to call full_clean()

        # This test documents the limitation
        live_key, key_hash, key_prefix = APIToken.generate_key(is_sandbox=False)

        # Bulk create would bypass our save() validation
        # This is a known Django limitation - document it
        # In production code, never use bulk_create for APIToken
        pass  # Documenting the risk

    def test_none_plaintext_always_allowed(self):
        """
        Both sandbox and live tokens can have plaintext_key=None.
        This should always be allowed.
        """
        # Test with sandbox token
        sandbox_key, key_hash1, key_prefix1 = APIToken.generate_key(is_sandbox=True)
        sandbox_token = APIToken.objects.create(
            tenant=self.tenant,
            name='Sandbox No Plaintext',
            key_hash=key_hash1,
            key_prefix=key_prefix1,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=True,
            plaintext_key=None  # Allowed
        )
        self.assertIsNone(sandbox_token.plaintext_key)

        # Test with live token
        live_key, key_hash2, key_prefix2 = APIToken.generate_key(is_sandbox=False)
        live_token = APIToken.objects.create(
            tenant=self.tenant,
            name='Live No Plaintext',
            key_hash=key_hash2,
            key_prefix=key_prefix2,
            scopes=['services:read'],
            created_by=self.user,
            is_sandbox=False,
            plaintext_key=None  # Allowed
        )
        self.assertIsNone(live_token.plaintext_key)
