# Copyright (c) Subzero Labs, Inc.
# SPDX-License-Identifier: Apache-2.0

"""
Cross-platform compatibility tests for the Rialo Python CDK.

Tests compatibility across different Python versions, operating systems,
and runtime environments.
"""

import os
import sys
import platform
import subprocess
import tempfile
import json
import pytest
from pathlib import Path
from rialo_py_cdk import (
    # Core functions
    generate_keypair,
    keypair_from_seed,
    keypair_from_mnemonic,
    generate_mnemonic,
    public_key_from_keypair,
    sign,
    verify,
    # Wallet and provider types
    Wallet,
    # Core types
    PublicKey,
    Hash,
    Signature,
    # Configuration
    Config,
    # Utility functions
    create_wallet_with_mnemonic,
    recover_wallet_from_mnemonic,
    rlo_to_kelvin,
    kelvin_to_rlo,
    # Constants
    KELVIN_PER_RLO,
    get_localnet_url,
    get_default_num_accounts,
    get_kelvin_per_rlo,
    # Exception
    RialoException,
)


class TestPlatformInformation:
    """Test platform information and basic functionality."""
    
    def test_python_version_compatibility(self):
        """Test that the CDK works with the current Python version."""
        python_version = sys.version_info
        
        print(f"\nPython Version: {python_version.major}.{python_version.minor}.{python_version.micro}")
        print(f"Platform: {platform.system()} {platform.release()}")
        print(f"Architecture: {platform.machine()}")
        print(f"Python Implementation: {platform.python_implementation()}")
        
        # Test basic functionality works
        keypair = generate_keypair()
        assert len(keypair) == 64
        
        # Should work with Python 3.8+ (adjust as needed)
        assert python_version.major == 3
        assert python_version.minor >= 8
    
    def test_platform_specific_constants(self):
        """Test that constants work across platforms."""
        # Network URLs should be valid
        localhost = get_localnet_url()
        
        assert isinstance(localhost, str) and localhost.startswith("http")
        
        # Numeric constants should be consistent
        assert KELVIN_PER_RLO > 0
        assert get_kelvin_per_rlo() == KELVIN_PER_RLO
        assert get_default_num_accounts() > 0
        
        print(f"  KELVIN_PER_RLO: {KELVIN_PER_RLO}")
        print(f"  Default accounts: {get_default_num_accounts()}")
        print(f"  Localhost URL: {localhost}")


class TestFileSystemCompatibility:
    """Test file system operations across different platforms."""
    
    def test_temporary_files_handling(self):
        """Test handling of temporary files and directories."""
        # Test with various file path scenarios
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            
            # Test path handling
            test_file = temp_path / "test_wallet.json"
            
            # Create test wallet data
            keypair = generate_keypair()
            wallet = Wallet("temp_test_wallet", keypair)
            
            # Write some test data
            wallet_data = {
                "name": wallet.name,
                "public_key": wallet.get_public_key_string(),
            }
            
            with open(test_file, 'w') as f:
                json.dump(wallet_data, f)
            
            # Read it back
            with open(test_file, 'r') as f:
                loaded_data = json.load(f)
            
            assert loaded_data["name"] == wallet.name
            assert loaded_data["public_key"] == wallet.get_public_key_string()
    
    def test_path_separator_handling(self):
        """Test that path separators work correctly across platforms."""
        # Test with different path styles
        if platform.system() == "Windows":
            # Test Windows-style paths
            test_path = "C:\\Users\\Test\\wallet.json"
            normalized = os.path.normpath(test_path)
            assert "\\" in normalized
        else:
            # Test Unix-style paths
            test_path = "/home/test/wallet.json"
            normalized = os.path.normpath(test_path)
            assert "/" in normalized
        
        # Test cross-platform path handling
        path_obj = Path("test") / "wallet" / "file.json"
        assert isinstance(str(path_obj), str)


class TestEncodingCompatibility:
    """Test string and byte encoding across platforms."""
    
    def test_unicode_handling(self):
        """Test Unicode string handling in wallet names and mnemonics."""
        # Test wallet with Unicode name
        keypair = generate_keypair()
        unicode_name = "tëst_wällët_🔐"
        wallet = Wallet(unicode_name, keypair)
        assert wallet.name == unicode_name
        
        # Test mnemonic with potential encoding issues
        mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
        derived_keypair = keypair_from_mnemonic(mnemonic)
        assert len(derived_keypair) == 64
        
        # Test with Unicode characters in messages
        unicode_message = "Hello 世界! 🌍".encode('utf-8')
        signature = sign(keypair, unicode_message)
        pubkey = public_key_from_keypair(keypair)
        assert verify(pubkey, unicode_message, signature)
    
    def test_binary_data_handling(self):
        """Test handling of binary data across platforms."""
        # Test with various binary patterns
        test_patterns = [
            b"\x00" * 32,  # All zeros
            b"\xFF" * 32,  # All ones
            bytes(range(32)),  # Sequential bytes
            bytes(reversed(range(32))),  # Reverse sequential
        ]
        
        for i, pattern in enumerate(test_patterns):
            # Test PublicKey creation
            pubkey = PublicKey(pattern)
            assert pubkey.to_bytes() == pattern
            
            # Test Hash creation
            hash_obj = Hash(pattern)
            assert hash_obj.to_bytes() == pattern
            
            # Test as seed for keypair generation
            keypair = keypair_from_seed(pattern)
            assert len(keypair) == 64
    
    def test_base58_encoding_consistency(self):
        """Test Base58 encoding consistency across platforms."""
        test_bytes = b"a" * 32
        pubkey = PublicKey(test_bytes)
        
        # Convert to string and back
        pubkey_string = str(pubkey)
        pubkey_from_string = PublicKey.from_string(pubkey_string)
        
        assert pubkey.to_bytes() == pubkey_from_string.to_bytes()
        
        # Test with different byte patterns (some patterns might not be valid for Base58)
        test_patterns = [
            b"a" * 32,  # Safe pattern
            b"b" * 32,  # Another safe pattern
            bytes([1] * 32),  # Simple pattern
        ]
        
        for pattern in test_patterns:
            try:
                original = PublicKey(pattern)
                string_repr = str(original)
                reconstructed = PublicKey.from_string(string_repr)
                assert original.to_bytes() == reconstructed.to_bytes()
            except RialoException as e:
                # Some patterns might not be valid, skip them
                if "after decoding" in str(e):
                    continue
                raise


class TestNumericalCompatibility:
    """Test numerical operations and precision across platforms."""
    
    def test_floating_point_consistency(self):
        """Test floating-point operations for unit conversions."""
        # Test RLO to kelvin conversions
        test_values = [
            0.0, 0.001, 0.1, 1.0, 10.0, 100.0, 1000.0,
            0.123456789, 3.141592653589793
        ]
        
        for rlo_value in test_values:
            kelvin_value = rlo_to_kelvin(rlo_value)
            rlo_back = kelvin_to_rlo(int(kelvin_value))
            
            # Should be consistent (within floating point precision)
            assert abs(rlo_back - rlo_value) < 1e-6
    
    def test_large_number_handling(self):
        """Test handling of large numbers within u64 range."""
        # Test with maximum values
        max_kelvin = 2**64 - 1
        rlo_value = kelvin_to_rlo(max_kelvin)
        assert rlo_value >= 0
        
        # Test with large RLO values
        large_rlo = 1e12  # 1 trillion RLO
        kelvin_value = rlo_to_kelvin(large_rlo)
        assert kelvin_value == large_rlo * KELVIN_PER_RLO
    
    def test_integer_overflow_handling(self):
        """Test handling of potential integer overflow scenarios."""
        # Test maximum safe integer values
        max_safe_int = 2**53 - 1  # JavaScript safe integer limit
        
        # Should handle large amounts
        if max_safe_int < 2**64 - 1:
            kelvin_amount = int(max_safe_int)
            rlo_amount = kelvin_to_rlo(kelvin_amount)
            assert rlo_amount >= 0


class TestConcurrencyCompatibility:
    """Test concurrency and threading compatibility."""
    
    
    def test_thread_safety_basic(self):
        """Test basic thread safety of cryptographic operations."""
        import threading
        import queue
        
        def crypto_worker(result_queue):
            """Worker function for cryptographic operations."""
            try:
                # Generate keypair
                keypair = generate_keypair()
                
                # Sign and verify
                message = b"thread safety test"
                signature = sign(keypair, message)
                pubkey = public_key_from_keypair(keypair)
                verified = verify(pubkey, message, signature)
                
                result_queue.put(("success", verified))
            except Exception as e:
                result_queue.put(("error", str(e)))
        
        # Run operations in multiple threads
        result_queue = queue.Queue()
        threads = []
        
        for _ in range(3):
            thread = threading.Thread(target=crypto_worker, args=(result_queue,))
            threads.append(thread)
            thread.start()
        
        # Wait for all threads to complete
        for thread in threads:
            thread.join()
        
        # Check results
        results = []
        while not result_queue.empty():
            results.append(result_queue.get())
        
        assert len(results) == 3
        for status, result in results:
            assert status == "success"
            assert result is True


class TestEnvironmentCompatibility:
    """Test compatibility with different runtime environments."""
    
    def test_environment_variables(self):
        """Test handling of environment variables."""
        # Test accessing environment variables
        path_env = os.environ.get("PATH")
        assert path_env is not None  # PATH should exist on all platforms
        
        # Test with custom environment variable
        test_key = "RIALO_CDK_TEST_VAR"
        test_value = "test_value_123"
        
        # Set environment variable
        os.environ[test_key] = test_value
        
        # Read it back
        read_value = os.environ.get(test_key)
        assert read_value == test_value
        
        # Clean up
        del os.environ[test_key]
    
    def test_working_directory_independence(self):
        """Test that CDK works regardless of current working directory."""
        original_cwd = os.getcwd()
        
        try:
            # Test from different directories
            with tempfile.TemporaryDirectory() as temp_dir:
                os.chdir(temp_dir)
                
                # Basic operations should still work
                keypair = generate_keypair()
                assert len(keypair) == 64
                
                # Wallet creation should work
                wallet = Wallet("cwd_test_wallet", keypair)
                assert wallet.name == "cwd_test_wallet"
                
        finally:
            # Always restore original working directory
            os.chdir(original_cwd)
    
    def test_python_executable_detection(self):
        """Test detection of Python executable and version."""
        # Should be able to detect Python executable
        python_exe = sys.executable
        assert python_exe is not None
        assert os.path.exists(python_exe)
        
        # Test version detection
        version_info = sys.version_info
        assert version_info.major >= 3
        
        print(f"  Python executable: {python_exe}")
        print(f"  Python version: {version_info.major}.{version_info.minor}.{version_info.micro}")


class TestMemoryManagementCompatibility:
    """Test memory management across different platforms."""
    
    def test_garbage_collection_compatibility(self):
        """Test that garbage collection works properly."""
        import gc
        import weakref
        
        # Create objects and test weak references
        keypairs = []
        weak_refs = []
        
        for _ in range(10):
            keypair = generate_keypair()
            wallet = Wallet(f"gc_test_wallet", keypair)
            keypairs.append(keypair)
            weak_refs.append(weakref.ref(wallet))
        
        # Clear strong references
        del keypairs
        
        # Force garbage collection
        gc.collect()
        
        # Some weak references should be cleared
        # (This is platform-dependent, so we just check it doesn't crash)
        cleared_count = sum(1 for ref in weak_refs if ref() is None)
        print(f"  Cleared {cleared_count}/{len(weak_refs)} weak references")
    
    def test_large_object_handling(self):
        """Test handling of large objects across platforms."""
        # Create large instruction data
        large_data = b"x" * (1024 * 1024)  # 1MB
        
        # Should be able to handle large binary data
        assert len(large_data) == 1024 * 1024
        
        # Test signing large messages
        keypair = generate_keypair()
        signature = sign(keypair, large_data)
        pubkey = public_key_from_keypair(keypair)
        
        # Verification should work with large data
        assert verify(pubkey, large_data, signature)


class TestErrorHandlingCompatibility:
    """Test error handling across different platforms."""
    
    def test_exception_consistency(self):
        """Test that exceptions are consistent across platforms."""
        from rialo_py_cdk import RialoException
        
        # Test invalid input errors
        with pytest.raises(RialoException) as exc_info:
            PublicKey(b"wrong_size")
        
        error = exc_info.value
        assert hasattr(error, 'code')
        assert hasattr(error, 'category')
        assert hasattr(error, 'message')
        assert error.code > 0
        
        print(f"  Error code: {error.code}")
        print(f"  Error category: {error.category}")
        print(f"  Error message: {error.message}")
    
    def test_error_message_encoding(self):
        """Test that error messages handle different character encodings."""
        from rialo_py_cdk import RialoException
        
        # Test with various invalid inputs that should generate errors
        test_cases = [
            (lambda: PublicKey(b""), "empty bytes"),
            (lambda: Hash(b"wrong"), "wrong size"),
            (lambda: Signature(b"too_short"), "invalid length"),
        ]
        
        for test_func, description in test_cases:
            with pytest.raises(RialoException) as exc_info:
                test_func()
            
            error_message = str(exc_info.value)
            assert isinstance(error_message, str)
            assert len(error_message) > 0
            
            # Error message should be readable
            assert any(char.isalpha() for char in error_message)


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