import time
import numpy as np
import subprocess
from pynput import keyboard
from sklearn.ensemble import IsolationForest

# --- CONFIGURATION ---
CALIBRATION_WINDOWS = 80     # Vectors needed for calibration
WINDOW_SIZE = 12            # Number of keystrokes per feature chunk
STRIKES_REQUIRED = 3        # Must fail 3 times IN A ROW to trigger lock
# ---------------------

pressed_keys = {}
dwell_buffer = []
flight_buffer = []
last_press_time = None

training_data = []
is_calibrating = True
consecutive_strikes = 0

# Isolation Forest with a balanced 3% threshold
model = IsolationForest(contamination=0.03, random_state=42)

def is_standard_char(key):
    """Filter out special keys like Shift, Backspace, Cmd to prevent false anomalies"""
    return hasattr(key, 'char') and key.char is not None

def prompt_mac_password():
    """Displays a native macOS password dialog to verify identity after a lock"""
    applescript = '''
    display dialog "Biometric Lock Alert: Anomaly detected!\n\nEnter your Mac password to authenticate and resume:" default answer "" with hidden answer buttons {"Quit App", "Authenticate"} default button "Authenticate"
    '''
    try:
        res = subprocess.run(['osascript', '-e', applescript], capture_output=True, text=True)
        output = res.stdout.strip()
        
        if "button returned:Authenticate" in output:
            parts = output.split("text returned:")
            if len(parts) > 1:
                entered_pass = parts[1]
                # Test the entered password against macOS system auth
                check = subprocess.run(['sudo', '-S', '-k', 'true'], input=f"{entered_pass}\n", text=True, capture_output=True)
                if check.returncode == 0:
                    print("\n[+] Password verified. Resuming biometric security.")
                    return True
                else:
                    print("\n[-] Incorrect password!")
        return False
    except Exception:
        return False

def trigger_security_lock():
    global consecutive_strikes, dwell_buffer, flight_buffer
    print("\n[!!!] BEHAVIORAL MISMATCH CONFIRMED: LOCKING MACBOOK [!!!]")
    
    # 1. Lock screen via macOS shortcut
    subprocess.run(['osascript', '-e', 'tell application "System Events" to keystroke "q" using {control down, command down}'])
    
    # 2. Reset buffers
    dwell_buffer.clear()
    flight_buffer.clear()
    consecutive_strikes = 0
    
    # 3. Require Mac password authentication to resume
    authenticated = prompt_mac_password()
    if not authenticated:
        print("\n[!] Authentication failed or cancelled. Exiting security daemon.")
        exit(0)

def extract_features(dwells, flights):
    """Extracts mean and standard deviation of dwell and flight times"""
    if len(dwells) < 5 or len(flights) < 5:
        return None
    return [
        np.mean(dwells), np.std(dwells),
        np.mean(flights), np.std(flights)
    ]

def on_press(key):
    global last_press_time
    if not is_standard_char(key):
        return  # Ignore non-character keys
        
    current_time = time.time()
    pressed_keys[key] = current_time
    
    if last_press_time is not None:
        flight = current_time - last_press_time
        if 0.01 < flight < 1.2:  # Ignore long pauses over 1.2s
            flight_buffer.append(flight)
            
    last_press_time = current_time

def on_release(key):
    global is_calibrating, dwell_buffer, flight_buffer, consecutive_strikes
    if not is_standard_char(key):
        return
        
    current_time = time.time()
    
    if key in pressed_keys:
        dwell = current_time - pressed_keys.pop(key)
        if 0.01 < dwell < 0.8:
            dwell_buffer.append(dwell)
            
        # Process feature vector when window is full
        if len(dwell_buffer) >= WINDOW_SIZE and len(flight_buffer) >= WINDOW_SIZE:
            features = extract_features(dwell_buffer[-WINDOW_SIZE:], flight_buffer[-WINDOW_SIZE:])
            
            if features is None:
                return
                
            if is_calibrating:
                training_data.append(features)
                print(f"Calibration Progress: {len(training_data)}/{CALIBRATION_WINDOWS} samples collected...", end="\r")
                
                if len(training_data) >= CALIBRATION_WINDOWS:
                    print("\n[+] Training AI baseline on your typing rhythm...")
                    model.fit(training_data)
                    print("[+] SYSTEM ARMED & ACTIVE. Monitoring in background.")
                    is_calibrating = False
                    dwell_buffer.clear()
                    flight_buffer.clear()
            else:
                # Live Evaluation Mode
                prediction = model.predict([features])
                
                if prediction[0] == -1:
                    consecutive_strikes += 1
                    print(f"\n[Warning] Anomaly strike {consecutive_strikes}/{STRIKES_REQUIRED}")
                    if consecutive_strikes >= STRIKES_REQUIRED:
                        trigger_security_lock()
                else:
                    consecutive_strikes = max(0, consecutive_strikes - 1)
                    print("User verified. Rhythm normal.", end="\r")
                    
                # Slide window
                dwell_buffer = dwell_buffer[-6:]
                flight_buffer = flight_buffer[-6:]

print("=== Behavioral Biometrics Lock V3 ===")
print("Type naturally (sentences, code, or paragraphs) to calibrate your profile.")
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()