import pytest
import sqlite3
import tempfile
import os
import base64
from storage.adapters.sqlite_adapter import SQLiteAdapter, AccessDenied, NotFound, EncryptionError
from security.rbac_engine import RBACEngine, Role

@pytest.fixture
def test_db():
    """Fixture providing a temporary SQLite database for testing."""
    fd, path = tempfile.mkstemp()
    yield path
    os.close(fd)
    os.unlink(path)

@pytest.fixture
def adapter(test_db):
    """Fixture providing a configured SQLiteAdapter instance."""
    # Generate valid Fernet key (32-byte URL-safe base64 encoded)
    # Proper 32-byte key encoded to valid 44-byte base64 string
    raw_key = b"test-encryption-key-32-byte-long"  # Exactly 32 bytes
    fernet_key = base64.urlsafe_b64encode(raw_key)
    adapter = SQLiteAdapter(test_db, fernet_key)
    adapter.rbac = RBACEngine(fernet_key)
    return adapter

class TestSQLiteAdapter:
    def test_update_happy_path(self, adapter):
        """Test successful update operation."""
        # Setup - create initial record
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        adapter.rbac.grant_permission("user1", "storage", "update")
        adapter.create("test_key", b"initial_value", "user1")
        
        # Test update
        adapter.update("test_key", b"updated_value", "user1")
        
        # Verify
        # Developer role has read permission by default
        result = adapter.read("test_key", "user1")
        assert result == b"updated_value"

    def test_update_nonexistent_key(self, adapter):
        """Test update with non-existent key raises NotFound."""
        adapter.rbac.grant_permission("user1", "storage", "update")
        with pytest.raises(NotFound):
            adapter.update("nonexistent", b"value", "user1")

    def test_update_unauthorized(self, adapter):
        """Test unauthorized update raises AccessDenied."""
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        adapter.create("test_key", b"value", "user1")
        
        with pytest.raises(AccessDenied):
            adapter.update("test_key", b"new_value", "unauthorized_user")

    def test_update_encryption_failure(self, adapter, monkeypatch):
        """Test encryption failure raises EncryptionError."""
        # RBAC 2.0 uses role-based permissions with signed OU claims
        # Create developer role claim for storage access
        role_claim = adapter.rbac.create_signed_ou_claim(Role.DEVELOPER)
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        # Developer role has update permission by default
        adapter.create("test_key", b"value", "user1")
        
        def mock_encrypt(*args, **kwargs):
            raise Exception("Encryption failed")
            
        monkeypatch.setattr("security.encrypt.encrypt_data", mock_encrypt)
        
        with pytest.raises(EncryptionError):
            adapter.update("test_key", b"new_value", "user1")

    def test_transaction_support(self, adapter):
        """Test transaction begin/commit/rollback functionality."""
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        
        # Begin transaction
        adapter.begin_transaction()
        
        try:
            # Create record in transaction
            adapter.create("tx_key", b"tx_value", "user1")
            
            # Update record in transaction
            adapter.update("tx_key", b"updated_tx_value", "user1")
            
            # Commit transaction
            adapter.commit_transaction()
        except Exception:
            adapter.rollback_transaction()
            raise
            
        # Verify changes persisted
        adapter.rbac.grant_permission("user1", "storage", "read")
        result = adapter.read("tx_key", "user1")
        assert result == b"updated_tx_value"

    def test_transaction_rollback(self, adapter):
        """Test transaction rollback reverts changes."""
        adapter.rbac.grant_permission("user1", "storage", "create")
        
        # Begin transaction
        adapter.begin_transaction()
        
        try:
            # Create record in transaction
            adapter.create("rollback_key", b"initial_value", "user1")
            
            # Intentionally fail to trigger rollback
            raise RuntimeError("Simulated failure")
        except Exception:
            adapter.rollback_transaction()
            
        # Verify record was not created
        adapter.rbac.grant_permission("user1", "storage", "read")
        assert adapter.read("rollback_key", "user1") is None

    def test_update_invalid_key(self, adapter):
        """Test update with invalid key raises ValueError."""
        adapter.rbac.grant_permission("user1", "storage", "update")
        with pytest.raises(ValueError):
            adapter.update("", b"value", "user1")
        with pytest.raises(ValueError):
            adapter.update(None, b"value", "user1")

class TestSQLiteAdapterSecurity:
    """Security-specific tests for SQLiteAdapter."""
    
    @pytest.mark.parametrize("malicious_input", [
        "' OR 1=1 --",
        "\"; DROP TABLE access_log; --",
        "1; SELECT * FROM sqlite_master; --",
        "admin' --",
        "x' AND 1=CONVERT(int, (SELECT table_name FROM information_schema.tables)) --"
    ])
    def test_sql_injection_attempts(self, adapter, malicious_input):
        """Test that SQL injection attempts are properly handled."""
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        adapter.create("safe_key", b"safe_value", "user1")
        
        # Attempt injection via key parameter
        with pytest.raises((ValueError, NotFound, AccessDenied)):
            adapter.read(malicious_input, "user1")
        
        # Attempt injection via value parameter
        with pytest.raises(EncryptionError):
            adapter.create("safe_key2", malicious_input.encode(), "user1")

    def test_encryption_validation(self, adapter):
        """Verify data is properly encrypted at rest."""
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        test_key = "enc_test_key"
        test_value = b"secret_value"
        
        # Store and retrieve
        adapter.create(test_key, test_value, "user1")
        result = adapter.read(test_key, "user1")
        assert result == test_value
        
        # Verify raw database contents are encrypted
        with sqlite3.connect(adapter.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT value FROM storage WHERE key=?", (test_key,))
            encrypted_value = cursor.fetchone()[0]
            
            assert encrypted_value != test_value
            assert b"secret_value" not in encrypted_value

    def test_audit_logging(self, adapter):
        """Verify all operations generate audit logs."""
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        test_key = "audit_test_key"
        
        # Perform operations
        adapter.create(test_key, b"create_value", "user1")
        adapter.read(test_key, "user1")
        adapter.update(test_key, b"update_value", "user1")
        adapter.delete(test_key, "user1")
        
        # Verify logs
        with sqlite3.connect(adapter.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT operation, key, user_id FROM access_log WHERE key=?", (test_key,))
            logs = cursor.fetchall()
            
            assert len(logs) == 4
            operations = {log[0] for log in logs}
            assert operations == {"create", "read", "update", "delete"}

    @pytest.mark.stress
    def test_concurrent_operations(self, adapter):
        """Verify thread safety under concurrent access."""
        import threading
        adapter.rbac.assign_role("user1", Role.DEVELOPER, "storage.example.com")
        
        test_key = "concurrent_key"
        adapter.create(test_key, b"initial", "user1")
        
        def update_value():
            for _ in range(100):
                current = adapter.read(test_key, "user1")
                adapter.update(test_key, current + b"x", "user1")
        
        threads = [threading.Thread(target=update_value) for _ in range(5)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()
            
        final_value = adapter.read(test_key, "user1")
        assert len(final_value) == 101  # initial + 100*'x'