"""
Tests for py-sdk/bsv/keys.py - PublicKey operations
Ported from ts-sdk/src/primitives/__tests/PublicKey.test.ts
"""

import pytest

from bsv.curve import Point
from bsv.keys import PrivateKey, PublicKey


class TestPublicKey:
    """Test cases for PublicKey class"""

    def setup_method(self):
        """Set up test fixtures"""
        self.private_key = PrivateKey(42)
        self.public_key = self.private_key.public_key()

    def test_public_key_from_private_key(self):
        """Test public key creation from private key"""
        assert isinstance(self.public_key, PublicKey)

        # Should be deterministic
        pub2 = self.private_key.public_key()
        assert self.public_key.hex() == pub2.hex()

    def test_public_key_from_hex_string(self):
        """Test public key creation from hex string"""
        pub_hex = self.public_key.hex()
        pub_from_hex = PublicKey(pub_hex)

        assert isinstance(pub_from_hex, PublicKey)
        assert pub_from_hex.hex() == pub_hex

    def test_public_key_from_bytes(self):
        """Test public key creation from bytes"""
        pub_bytes = self.public_key.serialize()
        pub_from_bytes = PublicKey(pub_bytes)

        assert isinstance(pub_from_bytes, PublicKey)
        assert pub_from_bytes.hex() == self.public_key.hex()

    def test_public_key_point_conversion(self):
        """Test conversion to/from curve point"""
        point = self.public_key.point()
        assert isinstance(point, Point)

        # Should be able to recreate public key from point
        pub_from_point = PublicKey(point)
        assert pub_from_point.hex() == self.public_key.hex()

    def test_public_key_serialization(self):
        """Test public key serialization"""
        # Test compressed serialization (default)
        compressed = self.public_key.serialize(compressed=True)
        assert isinstance(compressed, bytes)
        assert len(compressed) == 33  # Compressed format
        assert compressed[0] in [0x02, 0x03]  # Compressed prefix

        # Test uncompressed serialization
        uncompressed = self.public_key.serialize(compressed=False)
        assert isinstance(uncompressed, bytes)
        assert len(uncompressed) == 65  # Uncompressed format
        assert uncompressed[0] == 0x04  # Uncompressed prefix

    def test_public_key_hex_encoding(self):
        """Test public key hex encoding"""
        hex_str = self.public_key.hex()
        assert isinstance(hex_str, str)
        assert len(hex_str) == 66  # 33 bytes * 2 chars per byte (compressed)

        # Should start with 02 or 03 for compressed
        assert hex_str.startswith(("02", "03"))

    def test_shared_secret_derivation(self):
        """Test shared secret derivation from public key perspective"""
        alice_priv = PrivateKey(42)
        bob_priv = PrivateKey(69)

        alice_pub = alice_priv.public_key()
        bob_pub = bob_priv.public_key()

        # Test public key's derive_shared_secret method
        secret_from_pub = alice_pub.derive_shared_secret(bob_priv)
        secret_from_priv = alice_priv.derive_shared_secret(bob_pub)

        assert secret_from_pub == secret_from_priv

    def test_child_key_derivation(self):
        """Test child public key derivation"""
        counterparty_priv = PrivateKey(69)
        invoice_number = "test-invoice-123"

        # Derive child public key
        child_pub = self.public_key.derive_child(counterparty_priv, invoice_number)
        assert isinstance(child_pub, PublicKey)

        # Should be deterministic
        child_pub2 = self.public_key.derive_child(counterparty_priv, invoice_number)
        assert child_pub.hex() == child_pub2.hex()

        # Should match child derived from private key
        child_from_priv = self.private_key.derive_child(counterparty_priv.public_key(), invoice_number)
        assert child_pub.hex() == child_from_priv.public_key().hex()

    def test_message_verification(self):
        """Test message signature verification"""
        message = b"Hello, BSV!"

        # Check if sign_message method exists, otherwise skip detailed testing
        if hasattr(self.private_key, "sign_message") and hasattr(self.public_key, "verify_message_signature"):
            signature = self.private_key.sign_message(message)

            # Should verify correctly
            is_valid = self.public_key.verify_message_signature(message, signature)
            assert is_valid is True

            # Should fail with wrong message
            wrong_message = b"Wrong message"
            is_valid_wrong = self.public_key.verify_message_signature(wrong_message, signature)
            assert is_valid_wrong is False

            # Should fail with wrong signature
            wrong_signature = self.private_key.sign_message(wrong_message)
            is_valid_wrong_sig = self.public_key.verify_message_signature(message, wrong_signature)
            assert is_valid_wrong_sig is False
        else:
            # Skip detailed testing if methods don't match expected API
            assert hasattr(self.private_key, "sign") or hasattr(self.private_key, "ecdsa_sign")

    def test_address_generation(self):
        """Test Bitcoin address generation"""
        # Test P2PKH address
        address = self.public_key.address()
        assert isinstance(address, str)
        assert len(address) > 0
        assert address.startswith("1")  # Mainnet P2PKH prefix

        # Should be deterministic
        address2 = self.public_key.address()
        assert address == address2

    def test_invalid_public_key_creation(self):
        """Test that invalid public keys raise errors"""
        # Invalid hex string
        with pytest.raises(ValueError):
            PublicKey("invalid_hex")

        # Invalid point coordinates
        with pytest.raises(ValueError):
            invalid_point = Point(10, 13)  # Not on curve
            PublicKey(invalid_point)

    def test_public_key_equality(self):
        """Test public key equality comparison"""
        pub1 = self.private_key.public_key()
        pub2 = self.private_key.public_key()

        # Same private key should produce equal public keys
        assert pub1.hex() == pub2.hex()

        # Different private keys should produce different public keys
        other_priv = PrivateKey(69)
        other_pub = other_priv.public_key()
        assert pub1.hex() != other_pub.hex()

    def test_compressed_uncompressed_consistency(self):
        """Test that compressed and uncompressed formats represent the same key"""
        # Create public key from compressed format
        compressed_bytes = self.public_key.serialize(compressed=True)
        pub_from_compressed = PublicKey(compressed_bytes)

        # Create public key from uncompressed format
        uncompressed_bytes = self.public_key.serialize(compressed=False)
        pub_from_uncompressed = PublicKey(uncompressed_bytes)

        # Both should represent the same point
        assert pub_from_compressed.point().x == pub_from_uncompressed.point().x
        assert pub_from_compressed.point().y == pub_from_uncompressed.point().y


class TestCryptographicOperations:
    """Test cryptographic operations between private and public keys"""

    def test_ecdh_key_exchange(self):
        """Test ECDH key exchange protocol"""
        # Alice and Bob generate key pairs
        alice_priv = PrivateKey(42)
        bob_priv = PrivateKey(69)

        alice_pub = alice_priv.public_key()
        bob_pub = bob_priv.public_key()

        # Both derive the same shared secret
        alice_shared = alice_priv.derive_shared_secret(bob_pub)
        bob_shared = bob_priv.derive_shared_secret(alice_pub)

        assert alice_shared == bob_shared
        assert len(alice_shared) > 0

    def test_signature_roundtrip(self):
        """Test complete signature generation and verification"""
        priv = PrivateKey(42)
        pub = priv.public_key()

        # Only test if both methods exist
        if hasattr(priv, "sign_message") and hasattr(pub, "verify_message_signature"):
            messages = [
                b"Short message",
                b"A longer message with more content to test signature handling",
                b"",  # Empty message
                b"\x00\x01\x02\x03\xff",  # Binary data
            ]

            for message in messages:
                signature = priv.sign_message(message)
                is_valid = pub.verify_message_signature(message, signature)
                assert is_valid is True
        else:
            # Skip detailed testing but verify basic functionality exists
            assert hasattr(priv, "sign") or hasattr(priv, "ecdsa_sign")

    def test_key_encoding_formats(self):
        """Test various key encoding formats"""
        priv = PrivateKey(42)
        pub = priv.public_key()

        # Test private key formats
        hex_format = priv.hex()
        wif_format = priv.wif()

        # Should be able to recreate from both formats
        priv_from_hex = PrivateKey(bytes.fromhex(hex_format))  # Use bytes.fromhex for hex
        priv_from_wif = PrivateKey(wif_format)  # Use string constructor for WIF

        assert priv.hex() == priv_from_hex.hex()
        assert priv.hex() == priv_from_wif.hex()

        # Test public key formats
        pub_hex = pub.hex()
        pub_bytes = pub.serialize()

        pub_from_hex = PublicKey(pub_hex)
        pub_from_bytes = PublicKey(pub_bytes)

        assert pub.hex() == pub_from_hex.hex()
        assert pub.hex() == pub_from_bytes.hex()
