from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as sym_padding
from cryptography.hazmat.backends import default_backend
import os


# Helper functions for RSA encryption/decryption
def generate_rsa_keypair(key_size=2048):
    """
    Generates an RSA private-public key pair.
    """
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=key_size,
        backend=default_backend()
    )
    public_key = private_key.public_key()
    return private_key, public_key


def serialize_public_key(public_key):
    """
    Serializes a public key to PEM format.
    """
    return public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )


def deserialize_public_key(pem_data):
    """
    Deserializes a PEM-formatted public key.
    """
    return serialization.load_pem_public_key(
        pem_data,
        backend=default_backend()
    )


def rsa_encrypt(public_key, data):
    """
    Encrypts data using RSA public key with OAEP padding, handling chunking.
    Returns a list of encrypted chunks.
    """
    max_chunk_size = 190  # For 2048-bit keys with OAEP and SHA256
    encrypted_chunks = []
    # Split data into chunks
    for i in range(0, len(data), max_chunk_size):
        chunk = data[i:i + max_chunk_size]
        encrypted = public_key.encrypt(
            chunk,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        encrypted_chunks.append(encrypted)
    return encrypted_chunks


def rsa_decrypt(private_key, encrypted_chunks):
    """
    Decrypts a list of encrypted chunks using RSA private key with OAEP padding.
    Returns the concatenated decrypted data.
    """
    decrypted_data = b""
    for chunk in encrypted_chunks:
        decrypted = private_key.decrypt(
            chunk,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        decrypted_data += decrypted
    return decrypted_data


# Helper functions for AES encryption/decryption
def generate_aes_key():
    """
    Generates a random AES-256 key.
    """
    return os.urandom(32)  # 32 bytes = 256 bits


def aes_encrypt_bytes(key, plaintext_bytes):
    """
    Encrypts bytes using AES-256 in CBC mode with PKCS7 padding.
    Returns IV concatenated with ciphertext.
    """
    iv = os.urandom(16)  # 128-bit IV for AES
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = sym_padding.PKCS7(128).padder()
    padded_plaintext = padder.update(plaintext_bytes) + padder.finalize()
    ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
    return iv + ciphertext  # Prepend IV for decryption


def aes_decrypt_bytes(key, ciphertext_bytes):
    """
    Decrypts ciphertext using AES-256 in CBC mode with PKCS7 padding.
    Expects IV to be prepended to the ciphertext.
    Returns decrypted plaintext bytes.
    """
    iv = ciphertext_bytes[:16]
    actual_ciphertext = ciphertext_bytes[16:]
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    padded_plaintext = decryptor.update(actual_ciphertext) + decryptor.finalize()
    unpadder = sym_padding.PKCS7(128).unpadder()
    plaintext_bytes = unpadder.update(padded_plaintext) + unpadder.finalize()
    return plaintext_bytes


class User:
    def __init__(self, name):
        self.name = name
        self.private_key, self.public_key = generate_rsa_keypair()
        self.received_public_keys = []  # List to store received public keys
        self.aes_keys = []  # List to store shared AES keys for messaging

    def get_public_key_bytes(self):
        return serialize_public_key(self.public_key)

    def receive_encrypted_public_key(self, encrypted_pem_chunks, sender_private_key):
        """
        Receives and decrypts an encrypted public key.
        """
        decrypted_pem = rsa_decrypt(sender_private_key, encrypted_pem_chunks)
        sender_public_key = deserialize_public_key(decrypted_pem)
        self.received_public_keys.append(sender_public_key)
        print(f"{self.name} received and decrypted a public key.")

    def send_encrypted_public_key(self, recipient_public_key):
        """
        Encrypts the user's public key and prepares it for sending.
        """
        public_key_pem = self.get_public_key_bytes()
        encrypted_pem_chunks = rsa_encrypt(recipient_public_key, public_key_pem)
        return encrypted_pem_chunks

    def send_shared_aes_keys(self, recipient_public_key, num_keys=2):
        """
        Generates multiple AES-256 keys, encrypts each with the recipient's public RSA key.
        Returns a list of lists of encrypted AES key chunks and the generated AES keys.
        """
        encrypted_keys_list = []
        generated_keys = []
        for _ in range(num_keys):
            aes_key = generate_aes_key()
            encrypted_aes_key = rsa_encrypt(recipient_public_key, aes_key)  # List of encrypted chunks
            encrypted_keys_list.append(encrypted_aes_key)
            generated_keys.append(aes_key)
        print(f"{self.name} sent {num_keys} AES keys encrypted with recipient's public key.")
        return encrypted_keys_list, generated_keys

    def receive_shared_aes_keys(self, encrypted_keys_list, recipient_private_key, num_keys=2):
        """
        Receives and decrypts multiple encrypted AES keys.
        Each AES key is expected to be decrypted individually.
        """
        received_keys = []
        for encrypted_aes_key_chunks in encrypted_keys_list:
            decrypted_aes_key = rsa_decrypt(recipient_private_key, encrypted_aes_key_chunks)
            if len(decrypted_aes_key) != 32:
                raise ValueError(f"Invalid AES key length: expected 32 bytes, got {len(decrypted_aes_key)} bytes.")
            self.aes_keys.append(decrypted_aes_key)
            received_keys.append(decrypted_aes_key)
        print(f"{self.name} received and decrypted {num_keys} AES keys.")
        return received_keys

    def encrypt_message_double_aes(self, message, key1, key2):
        """
        Encrypts a message twice using two different AES keys.
        """
        # First encryption with key1
        encrypted1 = aes_encrypt_bytes(key1, message.encode())
        # Then encryption with key2
        encrypted2 = aes_encrypt_bytes(key2, encrypted1)
        return encrypted2

    def decrypt_message_double_aes(self, encrypted_message, key2, key1):
        """
        Decrypts a message that was encrypted twice using two different AES keys.
        """
        # First decryption with key2
        decrypted1 = aes_decrypt_bytes(key2, encrypted_message)
        # Then decryption with key1
        decrypted2 = aes_decrypt_bytes(key1, decrypted1)
        return decrypted2.decode()


def simulate_double_aes_encryption():
    # Initialize Users
    user1 = User("User1")
    user2 = User("User2")

    print("=== Key Generation ===")
    # Step 1: User1 sends public key to User2
    user1_pub_bytes = user1.get_public_key_bytes()
    print("User1 generated and shared public key with User2.")

    # Step 2: User2 receives User1's public key
    user1_public_key = deserialize_public_key(user1_pub_bytes)
    user2.received_public_keys.append(user1_public_key)
    print("User2 received User1's public key.")

    # Step 3: User2 sends public key to User1, encrypted with User1's public key
    encrypted_user2_pub_chunks = user2.send_encrypted_public_key(user1.public_key)
    print("User2 sent public key encrypted with User1's public key.")

    # Step 4: User1 decrypts and receives User2's public key
    user2_public_key = deserialize_public_key(
        rsa_decrypt(user1.private_key, encrypted_user2_pub_chunks)
    )
    user1.received_public_keys.append(user2_public_key)
    print("User1 received and decrypted User2's public key.")

    # Step 5: User1 generates two AES keys and sends them to User2 encrypted with User2's public key
    encrypted_aes_keys_user1_chunks, user1_aes_keys_generated = user1.send_shared_aes_keys(
        recipient_public_key=user2_public_key,
        num_keys=2
    )
    print("User1 sent 2 AES keys encrypted with User2's public key.")
    print("User1 encrypted and sent AES keys to User2.")

    # Step 6: User2 decrypts and receives the AES keys using their private key
    try:
        user2_aes_keys_received = user2.receive_shared_aes_keys(
            encrypted_keys_list=encrypted_aes_keys_user1_chunks,
            recipient_private_key=user2.private_key,
            num_keys=2
        )
    except ValueError as e:
        print(f"Error during AES key decryption by {user2.name}: {e}")
        return

    # Now, both User1 and User2 have access to two shared AES keys
    # User1 retains the generated AES keys
    user1.aes_keys = user1_aes_keys_generated
    # User2 has the received AES keys
    # No need for User2 to generate their own keys

    print("\n=== AES Keys Exchange Verification ===")
    # Display AES keys in hex format for verification (optional)
    print(f"User1's AES Keys (shared with User2):")
    for idx, key in enumerate(user1.aes_keys, start=1):
        print(f"  AES Key {idx}: {key.hex()}")

    print(f"User2's AES Keys received from User1:")
    for idx, key in enumerate(user2.aes_keys, start=1):
        print(f"  AES Key {idx}: {key.hex()}")

    print("\n=== Messaging ===")
    # User1 wants to send a message to User2
    message = "Hello User2! This is a secure and double encrypted message."
    print(f"Original Message: {message}")

    # Encrypt the message double with User1's AES keys
    encrypted_message = user1.encrypt_message_double_aes(
        message,
        key1=user1.aes_keys[0],
        key2=user1.aes_keys[1]
    )
    print("User1 encrypted the message with two AES keys.")

    # User2 decrypts the message using the same AES keys
    try:
        decrypted_message = user2.decrypt_message_double_aes(
            encrypted_message,
            key2=user2.aes_keys[1],
            key1=user2.aes_keys[0]
        )
        print(f"User2 decrypted the message: {decrypted_message}")
    except Exception as e:
        print(f"Error during message decryption by {user2.name}: {e}")


if __name__ == "__main__":
    simulate_double_aes_encryption() modify this code so who runs the script can input a message which will be sent from user 1 to user 2 (encrypted and decrypted) and then message can be inputted to be sent from user 2 to user 1