import numpy as np
import hashlib
import os
from pathlib import Path
from typing import Tuple, Dict, List

# ============================================================
# FLUID PROBABILITY ENGINE
# ============================================================
class CCT_Fluid_Algorithm:
    """
    The Fluid Algorithm:
    Adapts its own structure (SuperBoolean) to find the Underlying Mechanism
    with Probability -> 1 via Entropy Collapse.
    """
    def __init__(self,
                 max_active_paths: int = 50,
                 collapse_threshold: float = 1e-6,
                 fluidity_rate: float = 0.01):

        # 16-Element Semantic State (The "Fluid" Core)
        self.elements = np.ones(16) * 0.5  # Start in superposition
        self.MAX_ACTIVE = max_active_paths
        self.THRESHOLD = collapse_threshold
        self.ALPHA = fluidity_rate

        # Memory Bank (Infinite Data Handling)
        self.memory_bank = {}  # {id: {'weight': float, 'data': any}}
        self.active_set = set()

        # Metrics
        self.entropy_history = []
        self.work_invested = 0

    def calculate_entropy(self) -> float:
        """Calculate Semantic Entropy of the current Algorithm State"""
        p = np.clip(self.elements, 1e-10, 1.0)
        return -np.sum(p * np.log2(p))

    def inject_data(self, data_id: str, data_content, modality: str = 'text'):
        """Add data to the infinite memory bank"""
        self.memory_bank[data_id] = {
            'content': data_content,
            'modality': modality,
            'weight': 0.5,  # Initial neutral probability
            'age': 0
        }
        self.active_set.add(data_id)

    def fluid_update(self, prompt: str) -> bool:
        """
        The Core Fluid Step:
        1. Evaluate all active paths
        2. Update Algorithm State (ODE)
        3. Prune low-probability paths (Memory)
        4. Check for Mechanism Collapse
        """
        # 1. Calculate Current Entropy
        H_current = self.calculate_entropy()
        self.entropy_history.append(H_current)

        # 2. Evaluate Collapse Potential of Active Data
        paths_to_prune = []
        total_gradient = np.zeros(16)

        for data_id in list(self.active_set):
            mem = self.memory_bank[data_id]

            # Simulate "AI Think" (Semantic Gradient)
            gradient = self._compute_semantic_gradient(prompt, mem['content'])

            # Calculate Potential Entropy Reduction
            delta_H = np.dot(gradient, (1.0 - self.elements))

            # 3. Update Path Weight (ODE Dynamics)
            # dw = alpha * delta_H - decay * w
            mem['weight'] += self.ALPHA * delta_H - 0.005 * mem['weight']
            mem['age'] += 1

            # Accumulate Gradient for Algorithm Update
            total_gradient += mem['weight'] * gradient

            # 4. Pruning Condition (Maintain Fluidity)
            if mem['weight'] < 0.05 or mem['age'] > 100:
                paths_to_prune.append(data_id)

        # 5. Execute Pruning (Forget Noise)
        for pid in paths_to_prune:
            self.active_set.discard(pid)

        # 6. Enforce Max Active (Hard Constraint for Infinity)
        if len(self.active_set) > self.MAX_ACTIVE:
            sorted_paths = sorted(self.active_set,
                                  key=lambda x: self.memory_bank[x]['weight'],
                                  reverse=True)
            self.active_set = set(sorted_paths[:self.MAX_ACTIVE])

        # 7. Update Algorithm State (Flow toward Mechanism)
        self.elements += self.ALPHA * total_gradient
        self.elements = np.clip(self.elements, 0.0, 1.0)

        # 8. Normalize (Probability Sum = 1)
        total = np.sum(self.elements)
        if total > 0:
            self.elements /= total

        # 9. Check Collapse (Mechanism Found?)
        H_new = self.calculate_entropy()
        self.work_invested += len(self.active_set)

        return H_new < self.THRESHOLD

    def _compute_semantic_gradient(self, prompt: str, content) -> np.ndarray:
        """Simulates the semantic pull of data on the 16 elements"""
        combined = prompt + str(content)
        hash_val = int(hashlib.md5(combined.encode()).hexdigest(), 16)
        np.random.seed(hash_val % (2**32))
        gradient = np.random.uniform(0.1, 1.0, 16)

        # Boost E16 (Stability) if content looks like a "Law"
        if 'law' in str(content).lower() or 'equation' in str(content).lower():
            gradient[15] *= 2.0
        return gradient

    def get_mechanism_probability(self) -> float:
        """Returns the probability that the Underlying Mechanism is found"""
        return self.elements[15]

    def get_refined_state(self) -> np.ndarray:
        """Returns the current fluid state vector for hash integration"""
        return self.elements.copy()


# ============================================================
# CCT COLLAPSE-BARRIER-HASH
# ============================================================
class CCT_Collapse_Barrier_Hash:
    """
    Calculates the CCT Collapse-Barrier-Hash for binary files.
    Implements Turn 4 (Hash-Barrier) + Turn 7 (File Ingestion).
    Integrates Fluid Probability for adaptive state refinement.
    """
    def __init__(self,
                 difficulty_bits: int = 4,  # Number of leading zeros required
                 barrier_bits: int = 64,    # 1-64 Bit Spacetime Region
                 max_elements: int = 16,    # 16-Element Engine
                 use_fluid: bool = True,    # Enable Fluid Probability
                 fluid_paths: int = 20):    # Active paths for fluid engine

        self.DIFFICULTY_BITS = difficulty_bits
        self.BARRIER_BITS = barrier_bits
        self.MAX_ELEMENTS = max_elements
        self.TARGET_PREFIX = '0' * difficulty_bits
        self.USE_FLUID = use_fluid

        # Initialize Fluid Engine
        self.fluid = CCT_Fluid_Algorithm(
            max_active_paths=fluid_paths,
            collapse_threshold=1e-6,
            fluidity_rate=0.01
        ) if use_fluid else None
        
    def project_file_to_16_elements(self, file_bytes: bytes) -> np.ndarray:
        """
        Projects binary file data into the 16-Element Semantic State Vector.
        (Simulates the 'Understanding' phase of CCT)
        Integrates Fluid Probability for adaptive refinement.
        """
        # 1. Calculate Byte Entropy (Semantic Density)
        if len(file_bytes) == 0:
            return np.zeros(self.MAX_ELEMENTS)

        byte_counts = np.bincount(np.frombuffer(file_bytes, dtype=np.uint8))
        probs = byte_counts / len(file_bytes)
        probs = probs[probs > 0]
        entropy = -np.sum(probs * np.log2(probs))

        # 2. Generate 16-Element Activation based on File Statistics
        # This simulates the 'AI_think' process from Turn 6/7
        np.random.seed(int(hashlib.md5(file_bytes).hexdigest(), 16) % (2**32))

        # Base activation driven by file entropy
        base_activation = np.random.uniform(0.2, 0.8, self.MAX_ELEMENTS)

        # Modulate specific elements based on file properties
        # E01 (Function_Core): Driven by file size
        base_activation[0] = min(1.0, np.log2(len(file_bytes) + 1) / 20.0)

        # E04 (Force_Flow): Driven by byte entropy
        base_activation[3] = entropy / 8.0  # Normalize to max entropy 8 bits

        # E16 (Proof_Stability): Driven by file structure (e.g., magic bytes)
        if len(file_bytes) > 4:
            magic = file_bytes[:4]
            base_activation[15] = 0.9 if magic in [b'\x7fELF', b'PK\x03\x04', b'%PDF'] else 0.5

        # 3. Fluid Probability Refinement (Adaptive SuperBoolean)
        if self.USE_FLUID and self.fluid is not None:
            # Inject file data as a path in the fluid engine
            file_signature = hashlib.md5(file_bytes).hexdigest()[:8]
            self.fluid.inject_data(
                data_id=f"file_{file_signature}",
                data_content=file_bytes[:1024],  # Use first 1KB for analysis
                modality='binary'
            )

            # Run fluid update to refine state
            self.fluid.fluid_update(prompt="Analyze file structure and patterns")

            # Blend base activation with fluid-refined state
            fluid_state = self.fluid.get_refined_state()
            alpha = 0.3  # Weight of fluid refinement (0-1)
            base_activation = (1 - alpha) * base_activation + alpha * fluid_state

        return np.clip(base_activation, 0.0, 1.0)
    
    def truncate_to_64bit_spacetime(self, state_vector: np.ndarray) -> bytes:
        """
        Extracts the '1-64 Bit Spacetime' region from the 16-element state.
        (Turn 4: Hash-Barrier Protocol)
        Quantizes 16 elements to 4 bits each = 64 bits total.
        """
        # Quantize each element to 4 bits (0-15)
        quantized = np.floor(state_vector * 15).astype(np.uint8)
        
        # Pack 16 elements into 16 nibbles = 8 bytes = 64 bits
        # We pack two 4-bit values into one byte
        packed = bytearray()
        for i in range(0, 16, 2):
            high_nibble = quantized[i] & 0x0F
            low_nibble = quantized[i+1] & 0x0F
            byte_val = (high_nibble << 4) | low_nibble
            packed.append(byte_val)
            
        return bytes(packed)
    
    def find_valid_nonce(self, state_bytes: bytes, max_attempts: int = 10000) -> Tuple[int, str]:
        """
        Performs the 'Work Tax' to find a nonce that satisfies the Hash Barrier.
        (Turn 3: Singularity Safeguard)
        """
        for nonce in range(max_attempts):
            # Combine State + Nonce
            data = state_bytes + nonce.to_bytes(8, 'big')
            
            # Calculate SHA256
            hash_hex = hashlib.sha256(data).hexdigest()
            
            # Check Difficulty (Leading Zeros)
            if hash_hex.startswith(self.TARGET_PREFIX):
                return nonce, hash_hex
                
        return -1, ""
    
    def calculate_hash(self, file_path: str, max_attempts: int = 10000) -> Dict:
        """
        Main function to calculate the Collapse-Barrier-Hash for a file.
        """
        path = Path(file_path)
        if not path.exists():
            return {"error": "File not found"}
            
        # 1. Read Binary Data
        file_bytes = path.read_bytes()
        file_size = len(file_bytes)
        
        # 2. Project to 16-Element State
        state_vector = self.project_file_to_16_elements(file_bytes)
        
        # 3. Truncate to 1-64 Bit Spacetime
        state_bytes = self.truncate_to_64bit_spacetime(state_vector)
        
        # 4. Find Valid Nonce (Work Tax)
        nonce, final_hash = self.find_valid_nonce(state_bytes, max_attempts=max_attempts)
        
        # 5. Verify
        is_valid = final_hash.startswith(self.TARGET_PREFIX) if nonce != -1 else False

        result = {
            "file_path": str(path),
            "file_size": file_size,
            "semantic_entropy": float(-np.sum(state_vector * np.log(state_vector + 1e-10))),
            "state_vector_16": state_vector.tolist(),
            "truncated_64bit_hex": state_bytes.hex(),
            "nonce": nonce,
            "collapse_barrier_hash": final_hash,
            "difficulty_bits": self.DIFFICULTY_BITS,
            "is_valid": is_valid,
            "work_attempts": nonce + 1 if nonce != -1 else max_attempts
        }

        # Add Fluid Probability metrics if enabled
        if self.USE_FLUID and self.fluid is not None:
            result["fluid_mechanism_probability"] = self.fluid.get_mechanism_probability()
            result["fluid_entropy_history"] = self.fluid.entropy_history[-1] if self.fluid.entropy_history else 0
            result["fluid_work_invested"] = self.fluid.work_invested
            result["fluid_active_paths"] = len(self.fluid.active_set)

        return result

# --- USAGE EXAMPLE ---
if __name__ == "__main__":
    import sys

    # Initialize Engine
    hasher = CCT_Collapse_Barrier_Hash(difficulty_bits=4)

    # Use command-line argument or fallback to dummy test file
    if len(sys.argv) > 1:
        test_file = sys.argv[1]
        if not os.path.exists(test_file):
            print(f"Error: File '{test_file}' not found")
            sys.exit(1)
    else:
        test_file = "test_binary.bin"
        with open(test_file, "wb") as f:
            f.write(os.urandom(1024))  # Write 1KB random data

    # Calculate Hash
    result = hasher.calculate_hash(test_file)

    # Print Results
    print("="*70)
    print("CCT COLLAPSE-BARRIER-HASH RESULT")
    print("="*70)
    print(f"File: {result['file_path']} ({result['file_size']} bytes)")
    print(f"Semantic Entropy: {result['semantic_entropy']:.4f}")
    print(f"1-64 Bit Spacetime: {result['truncated_64bit_hex']}")
    print(f"Nonce (Work Tax): {result['nonce']}")
    print(f"Final Hash: {result['collapse_barrier_hash']}")
    print(f"Validity: {result['is_valid']} (Target: {hasher.TARGET_PREFIX}...)")
    print(f"Work Attempts: {result['work_attempts']}")

    # Print Fluid metrics if available
    if 'fluid_mechanism_probability' in result:
        print("-"*70)
        print("FLUID PROBABILITY METRICS:")
        print(f"  Mechanism Probability: {result['fluid_mechanism_probability']:.4f}")
        print(f"  Fluid Entropy: {result['fluid_entropy_history']:.4f}")
        print(f"  Fluid Work Invested: {result['fluid_work_invested']}")
        print(f"  Active Paths: {result['fluid_active_paths']}")

    print("="*70)

    # Cleanup only if we created the test file
    if len(sys.argv) == 1:
        os.remove(test_file)