import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from obsidian256 import (
    obsidian3300,
    quantumstate,
    chaosengine,
    nonlinearfeedback,
    dynamicsbox,
    contextboundkey,
)


def test_quantum_state():
    qs = quantumstate()
    qs.absorb(b"test data")
    output = qs.squeeze(32)
    assert len(output) == 32
    assert output != b"\x00" * 32


def test_chaos_engine():
    ce = chaosengine(0x123456789abcdef0)
    val1 = ce.generate()
    val2 = ce.generate()
    assert val1 != val2


def test_nonlinear_feedback():
    nf = nonlinearfeedback(0x1111111111111111, 0x2222222222222222, 0x3333333333333333)
    val1 = nf.clock()
    val2 = nf.clock()
    assert val1 != val2


def test_dynamic_sbox():
    key = b"test key for sbox generation"
    sbox = dynamicsbox(key)
    
    data = bytes(range(256))
    substituted = sbox.substitute(data)
    restored = sbox.inverse_substitute(substituted)
    
    assert restored == data


def test_context_bound_key():
    cb = contextboundkey()
    context = cb.get_context()
    assert len(context) > 0
    
    master_key = os.urandom(32)
    bound_key, ctx = cb.derive_bound_key(master_key)
    
    assert len(bound_key) == len(master_key)
    assert bound_key != master_key


def test_obsidian3300_basic():
    cipher = obsidian3300()
    
    plaintext = b"test message for 3300-bit encryption"
    ciphertext = cipher.encrypt(plaintext)
    decrypted = cipher.decrypt(ciphertext)
    
    assert decrypted == plaintext


def test_obsidian3300_context_bound():
    cipher = obsidian3300(context_bound=True)
    
    plaintext = b"context bound encryption test"
    ciphertext = cipher.encrypt(plaintext)
    decrypted = cipher.decrypt(ciphertext)
    
    assert decrypted == plaintext
    assert cipher.get_context() is not None


def test_obsidian3300_with_iv():
    cipher = obsidian3300()
    
    plaintext = b"encryption with iv prepended"
    data = cipher.encrypt_with_iv(plaintext)
    
    cipher2 = obsidian3300(key=cipher.get_key())
    decrypted = cipher2.decrypt_with_iv(data)
    
    assert decrypted == plaintext


def test_obsidian3300_various_sizes():
    cipher = obsidian3300()
    
    sizes = [1, 100, 500, 1000]
    for size in sizes:
        plaintext = os.urandom(size)
        ciphertext = cipher.encrypt(plaintext)
        decrypted = cipher.decrypt(ciphertext)
        assert decrypted == plaintext


def test_avalanche_3300():
    key1 = b"\x00" * 413
    key2 = b"\x01" + b"\x00" * 412
    
    cipher1 = obsidian3300(key=key1)
    cipher2 = obsidian3300(key=key2)
    
    cipher2._iv = cipher1._iv
    cipher2._round_keys = cipher2._generate_round_keys()
    
    plaintext = b"\x00" * 413
    ct1 = cipher1.encrypt(plaintext)
    ct2 = cipher2.encrypt(plaintext)
    
    diff_bits = sum(bin(b1 ^ b2).count("1") for b1, b2 in zip(ct1, ct2))
    total_bits = len(ct1) * 8
    
    percentage = (diff_bits / total_bits) * 100
    print(f"3300-bit avalanche: {diff_bits}/{total_bits} ({percentage:.2f}%)")
    
    assert percentage > 30


if __name__ == "__main__":
    test_quantum_state()
    print("test_quantum_state: passed")
    
    test_chaos_engine()
    print("test_chaos_engine: passed")
    
    test_nonlinear_feedback()
    print("test_nonlinear_feedback: passed")
    
    test_dynamic_sbox()
    print("test_dynamic_sbox: passed")
    
    test_context_bound_key()
    print("test_context_bound_key: passed")
    
    test_obsidian3300_basic()
    print("test_obsidian3300_basic: passed")
    
    test_obsidian3300_context_bound()
    print("test_obsidian3300_context_bound: passed")
    
    test_obsidian3300_with_iv()
    print("test_obsidian3300_with_iv: passed")
    
    test_obsidian3300_various_sizes()
    print("test_obsidian3300_various_sizes: passed")
    
    test_avalanche_3300()
    print("test_avalanche_3300: passed")
    
    print("\nall advanced tests passed!")
