from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib
import hmac
import json

def encrypt(data, key):
    if isinstance(data, dict):
        data = json.dumps(data)
    if not isinstance(data, str):
        raise ValueError("Data must be a string or a dictionary")

    data = data.encode('utf-8')

    # Create a new AES cipher with the key and a random IV
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC)
    iv = cipher.iv

    # Pad the data to make it a multiple of the AES block size
    padded_data = pad(data, AES.block_size)

    # Encrypt the padded data
    encrypted_data = cipher.encrypt(padded_data)

    # Encode the IV and encrypted data as Base64 and concatenate them
    encrypted_data_b64 = b64encode(iv + encrypted_data).decode('utf-8')

    return encrypted_data_b64

def decrypt(enc_data_b64, key):
    # Decode the base64 data to retrieve the bytes
    enc_data_bytes = b64decode(enc_data_b64)

    # Extract the IV and the encrypted data
    iv = enc_data_bytes[:AES.block_size]
    encrypted_data = enc_data_bytes[AES.block_size:]

    # Create a new AES cipher with the key and the extracted IV
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv)

    # Decrypt the data
    decrypted_padded_data = cipher.decrypt(encrypted_data)

    # Unpad the decrypted data
    decrypted_data = unpad(decrypted_padded_data, AES.block_size)

    # Try to decode the decrypted data as UTF-8
    decrypted_data_str = decrypted_data.decode('utf-8')
    try:
        return json.loads(decrypted_data_str)
    except json.JSONDecodeError:
        return decrypted_data_str


def hash_to_hex(input_string, salt):
    if not isinstance(input_string, str):
        raise ValueError("Input must be a string")
    # Create a new SHA-256 hasher
    hasher = hashlib.sha256()
    # Update the hasher with the input string encoded to bytes
    hasher.update(input_string.encode('utf-8'))
    # Return the hexadecimal representation of the hash
    return hasher.hexdigest()


def derive_salt(digits, secret_key):
    """Derives a salt from the last 8 digits of the DIGITs using HMAC."""
    last_8_digits = digits[-8:]
    return hmac.new(secret_key, last_8_digits.encode(), hashlib.sha256).digest()[:16]  # Use first 16 bytes

def hash_digits(digits, secret_key):
    """Hashes the PAN using a derived salt without storing it."""
    salt = derive_salt(digits, secret_key)
    hash_obj = hashlib.sha256(salt + digits.encode())
    return hash_obj.hexdigest()
