#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Quantum Eraser – AUTONOMOUS ATTACK SYSTEM (v4.0)
================================================
Acest script simuleaza un atacator care nu stie nimic initial.
1. Genereaza un experiment secret (True Key necunoscuta).
2. Incepe sa "sape" singur dupa cheie, testand ipoteze.
3. Ajusteaza automat strategia (Brute-force -> Genetic).
"""

import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import hashlib
import threading
import time
import random
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

# ==================== SIMULATION ENGINE ====================

def generate_keystream_fast(key_int: int, length: int) -> np.ndarray:
    """Generator rapid deterministic pentru demo."""
    # Folosim un seed bazat pe cheie pentru viteza in demo
    np.random.seed(key_int)
    return np.random.randint(0, 2, length, dtype=np.int8)

def evaluate_candidate_vectorized(args):
    """Evalueaza o cheie candidata (worker process)."""
    key_int, key_len, s_bits, cum_shots = args
    
    # Reconstruim Idler-ul presupus
    I_guess = generate_keystream_fast(key_int, len(s_bits))
    
    # Calculam vizibilitatea medie
    scores = []
    idx_start = 0
    
    # Optimizare masiva: calculam diferenta de probabilitate doar pe chunk-uri
    for idx_end in cum_shots[1:]:
        seg_I = I_guess[idx_start:idx_end]
        seg_S = s_bits[idx_start:idx_end]
        
        # Count-uri
        m0 = (seg_I == 0)
        c0 = np.sum(m0)
        c1 = len(seg_S) - c0
        
        if c0 > 0 and c1 > 0:
            p0 = np.sum(seg_S[m0]) / c0
            p1 = np.sum(seg_S[~m0]) / c1
            scores.append(abs(p0 - p1))
        
        idx_start = idx_end
        
    if not scores: return 0.0, key_int
    return np.mean(scores), key_int

# ==================== GUI & CONTROLLER ====================

class AutoHackerApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Quantum Eraser – Autonomous Breach System")
        self.geometry("1280x800")
        self.state('zoomed')
        self.style = ttk.Style(self)
        self.style.theme_use('clam')
        
        # Culori "Hacker"
        self.configure(bg="#0f0f0f")
        self.style.configure(".", background="#0f0f0f", foreground="#00ff00", font=("Consolas", 10))
        self.style.configure("TLabel", background="#0f0f0f", foreground="#00ff00")
        self.style.configure("TButton", background="#222", foreground="#00ff00", borderwidth=1)
        self.style.map("TButton", background=[('active', '#444')])
        self.style.configure("TLabelframe", background="#0f0f0f", foreground="#00aa00", bordercolor="#005500")
        self.style.configure("TLabelframe.Label", background="#0f0f0f", foreground="#00ff00")

        # Stare sistem
        self.running = False
        self.experiment_data = None
        self.true_key_len = 0
        self.found_key = None
        
        self._setup_ui()
        
    def _setup_ui(self):
        # Header
        header = tk.Frame(self, bg="#001100", height=60)
        header.pack(fill=tk.X)
        tk.Label(header, text="QUANTUM BREACH // AUTONOMOUS AGENT", 
                 font=("Consolas", 24, "bold"), bg="#001100", fg="#00ff00").pack(pady=10)
        
        # Main Layout
        main_frame = tk.Frame(self, bg="#0f0f0f")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
        
        # Left: Controls & Log
        left_col = tk.Frame(main_frame, bg="#0f0f0f", width=400)
        left_col.pack(side=tk.LEFT, fill=tk.Y, padx=10)
        
        # Target Setup
        grp_target = ttk.LabelFrame(left_col, text=" [1] TARGET SYSTEM SIMULATION ")
        grp_target.pack(fill=tk.X, pady=10)
        
        ttk.Label(grp_target, text="Target Complexity (Key Bits):").pack(anchor="w", padx=10)
        self.scale_diff = tk.Scale(grp_target, from_=8, to=32, orient=tk.HORIZONTAL, 
                                   bg="#0f0f0f", fg="#00ff00", troughcolor="#222", highlightthickness=0)
        self.scale_diff.set(16)
        self.scale_diff.pack(fill=tk.X, padx=10, pady=5)
        
        self.btn_start = ttk.Button(grp_target, text="INITIATE SIMULATION & AUTO-ATTACK", command=self.start_sequence)
        self.btn_start.pack(fill=tk.X, padx=10, pady=10)
        
        # Attack Status
        grp_status = ttk.LabelFrame(left_col, text=" [2] ATTACK VECTORS ")
        grp_status.pack(fill=tk.X, pady=10)
        
        self.lbl_phase = ttk.Label(grp_status, text="STATUS: IDLE", font=("Consolas", 12, "bold"))
        self.lbl_phase.pack(anchor="w", padx=10, pady=5)
        
        self.lbl_strategy = ttk.Label(grp_status, text="STRATEGY: None")
        self.lbl_strategy.pack(anchor="w", padx=10)
        
        self.lbl_progress = ttk.Label(grp_status, text="KEYS TESTED: 0")
        self.lbl_progress.pack(anchor="w", padx=10)
        
        self.lbl_best = ttk.Label(grp_status, text="CURRENT VISIBILITY: 0.00%")
        self.lbl_best.pack(anchor="w", padx=10, pady=5)

        # Terminal Log
        ttk.Label(left_col, text=" [3] SYSTEM LOG ").pack(anchor="w", pady=(20,0))
        self.log_text = tk.Text(left_col, height=20, width=50, bg="black", fg="#00ff00", 
                                font=("Consolas", 9), relief="flat", borderwidth=1)
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # Right: Visualization
        right_col = tk.Frame(main_frame, bg="#0f0f0f")
        right_col.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10)
        
        # Signal Plot
        self.fig = Figure(figsize=(5, 4), dpi=100, facecolor="#0f0f0f")
        self.ax = self.fig.add_subplot(111)
        self.ax.set_facecolor("black")
        self.ax.spines['bottom'].set_color('#00ff00')
        self.ax.spines['top'].set_color('#00ff00') 
        self.ax.spines['right'].set_color('#00ff00')
        self.ax.spines['left'].set_color('#00ff00')
        self.ax.tick_params(axis='x', colors='#00ff00')
        self.ax.tick_params(axis='y', colors='#00ff00')
        self.ax.set_title("SIGNAL INTERFERENCE RECOVERY", color="#00ff00")
        
        self.canvas = FigureCanvasTkAgg(self.fig, right_col)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # Progress Bar
        self.pbar = ttk.Progressbar(right_col, mode='indeterminate')
        self.pbar.pack(fill=tk.X, pady=10)

    def log(self, msg):
        ts = time.strftime("%H:%M:%S")
        self.log_text.insert(tk.END, f"[{ts}] {msg}\n")
        self.log_text.see(tk.END)

    def start_sequence(self):
        if self.running: return
        
        # 1. Setup Target
        target_bits = self.scale_diff.get()
        self.true_key_len = target_bits
        self.log(f"initializing target system... {target_bits}-bit encryption.")
        
        # Generate hidden experiment data
        threading.Thread(target=self._generate_and_run, args=(target_bits,), daemon=True).start()

    def _generate_and_run(self, bits):
        self.running = True
        self.btn_start.config(state="disabled")
        self.pbar.start(10)
        
        # --- STEP 1: Create the 'Hidden' Reality ---
        true_key = random.getrandbits(bits)
        self.log(f"TARGET LOCKED. Hidden Key Generated.")
        
        # Parameters
        n_angles = 20
        shots = 2000
        phis = np.linspace(0, 2*np.pi, n_angles)
        
        # Generate Signal (S) based on True Key (I)
        np.random.seed(true_key) 
        full_idler = np.random.randint(0, 2, shots * n_angles, dtype=np.int8)
        
        s_bits = []
        cum_shots = [0]
        idx = 0
        
        # Physics simulation
        for phi in phis:
            seg_I = full_idler[idx : idx+shots]
            rnd = np.random.rand(shots)
            
            # Quantum Logic:
            # If I=0 (Path A known) -> Interference Pattern (cos^2)
            # If I=1 (Path B known) -> No Interference (0.5)
            # *Aceasta este o simplificare pentru demo*
            
            prob_interference = (1 + np.cos(phi)) / 2
            
            seg_S = np.zeros(shots, dtype=np.int8)
            
            mask_0 = (seg_I == 0)
            mask_1 = (seg_I == 1)
            
            seg_S[mask_0] = (rnd[mask_0] < prob_interference).astype(np.int8)
            seg_S[mask_1] = (rnd[mask_1] < 0.5).astype(np.int8) # Noise
            
            s_bits.extend(seg_S)
            cum_shots.append(cum_shots[-1] + shots)
            idx += shots
            
        self.experiment_data = (np.array(s_bits), np.array(cum_shots), phis)
        self.log("Data stream intercepted. Begin decryption analysis...")
        
        # --- STEP 2: AUTO-HACK LOGIC ---
        self._auto_attack_loop(true_key)

    def _auto_attack_loop(self, true_key):
        """Creierul atacului automat."""
        
        # Strategy 1: Quick Scan (Brute Force on small keys)
        # Incercam ipoteza: "Poate cheia e mica?"
        
        test_lengths = [8, 10, 12, 14, 16, 20, 24, 28, 32]
        # Filtram sa nu mergem mult peste target (in realitate nu stim targetul, dar pentru demo e ok sa limitam)
        test_lengths = [l for l in test_lengths if l <= self.true_key_len + 4]
        
        found = False
        
        pool = ProcessPoolExecutor(max_workers=multiprocessing.cpu_count())
        
        for length in test_lengths:
            if found: break
            
            self.lbl_phase.config(text=f"PHASE: PROBING LEN={length}")
            
            # Decide method
            method = "BRUTE-FORCE" if length <= 14 else "GENETIC-ALGORITHM"
            self.lbl_strategy.config(text=f"STRATEGY: {method}")
            self.log(f"Trying key_len={length} with {method}...")
            
            if method == "BRUTE-FORCE":
                # Brute force rapid pe spațiu mic
                space = 1 << length
                # Daca spatiul e mic (< 4096), testam tot. Altfel testam random sample.
                if space < 8000:
                    candidates = list(range(space))
                else:
                    candidates = [random.getrandbits(length) for _ in range(5000)]
                
                results = list(pool.map(evaluate_candidate_vectorized, 
                                      [(c, length, self.experiment_data[0], self.experiment_data[1]) for c in candidates]))
                
                best_score = max(results, key=lambda x: x[0])
                self._update_gui(best_score[0], best_score[1], length)
                
                if best_score[0] > 0.8: # Prag succes
                    found = True
                    self._success_sequence(best_score[1], length)
                    
            else:
                # GENETIC ALGORITHM
                # Parametri dinamici
                pop_size = 500 if length < 20 else 1500
                generations = 30 if length < 20 else 80
                
                population = [random.getrandbits(length) for _ in range(pop_size)]
                
                for gen in range(generations):
                    # Evaluare paralela
                    tasks = [(c, length, self.experiment_data[0], self.experiment_data[1]) for c in population]
                    results = list(pool.map(evaluate_candidate_vectorized, tasks))
                    results.sort(key=lambda x: x[0], reverse=True)
                    
                    best_gen = results[0]
                    self._update_gui(best_gen[0], best_gen[1], length)
                    
                    if best_gen[0] > 0.9: # BINGO
                        found = True
                        self._success_sequence(best_gen[1], length)
                        break
                    
                    # Evolution
                    if not found:
                        new_pop = [r[1] for r in results[:int(pop_size*0.1)]] # Elitism 10%
                        
                        while len(new_pop) < pop_size:
                            # Tournament
                            p1 = random.choice(results[:int(pop_size*0.4)])[1]
                            p2 = random.choice(results[:int(pop_size*0.4)])[1]
                            
                            # Crossover
                            mask = random.getrandbits(length)
                            child = (p1 & mask) | (p2 & ~mask)
                            
                            # Mutation (Adaptive)
                            if random.random() < 0.3:
                                child ^= (1 << random.randint(0, length-1))
                            
                            new_pop.append(child)
                        population = new_pop
                        
            if not found:
                self.log(f"Failed at len={length}. Increasing complexity...")

        pool.shutdown()
        if not found:
            self.log("ATTACK FAILED. Target too complex for current constraints.")
            self.running = False
            self.btn_start.config(state="normal")
            self.pbar.stop()

    def _update_gui(self, score, key_int, length):
        # Update labels (thread safe via after)
        self.after(0, lambda: self.lbl_best.config(text=f"VISIBILITY: {score*100:.2f}%"))
        self.after(0, lambda: self.lbl_progress.config(text=f"Testing Key: {bin(key_int)}"))
        
        # Update Plot
        self.after(0, lambda: self._plot_live(key_int, length))

    def _plot_live(self, key_int, length):
        s_bits, cum_shots, phis = self.experiment_data
        
        # Reconstruct pattern based on current BEST key
        I_guess = generate_keystream_fast(key_int, len(s_bits))
        
        vals = []
        idx_start = 0
        for idx_end in cum_shots[1:]:
            seg_I = I_guess[idx_start:idx_end]
            seg_S = s_bits[idx_start:idx_end]
            
            m0 = (seg_I == 0)
            if np.sum(m0) > 0:
                vals.append(np.mean(seg_S[m0]))
            else:
                vals.append(0.5)
            idx_start = idx_end
            
        self.ax.clear()
        self.ax.plot(phis, vals, 'o-', color='#00ff00', lw=2, label='Reconstructed Signal')
        self.ax.set_ylim(0, 1)
        self.ax.set_facecolor("black")
        self.ax.grid(color='#004400')
        self.ax.set_title(f"DECRYPTION LIVE FEED [KeyLen: {length}]", color="#00ff00")
        self.canvas.draw()

    def _success_sequence(self, key_int, length):
        self.log("!!! KEY PATTERN IDENTIFIED !!!")
        self.log(f"Decrypted Key: {bin(key_int)}")
        self.log("Interference Pattern RESTORED.")
        
        self.after(0, lambda: self.lbl_phase.config(text="STATUS: BREACH SUCCESSFUL", foreground="white", background="green"))
        self.after(0, lambda: messagebox.showinfo("SYSTEM HACKED", f"Cheia a fost găsită!\n\nKey: {bin(key_int)}\nLength: {length} bits"))
        self.running = False
        self.after(0, lambda: self.btn_start.config(state="normal"))
        self.after(0, self.pbar.stop)

if __name__ == "__main__":
    multiprocessing.freeze_support()
    app = AutoHackerApp()
    app.mainloop()
