#!/usr/bin/env python3
"""
╔══════════════════════════════════════════════════════════════╗
║  NEURONS — Marziel AI Micro-Process Architecture            ║
║                                                             ║
║  Inspired by biological neurons: lightweight workers that   ║
║  spawn → fire (execute) → die, freeing all resources.       ║
║                                                             ║
║  Three layers:                                              ║
║    Axon    — Long-lived core (LLM server, lazy-loaded)      ║
║    Dendrite — Medium-lived request handlers                  ║
║    Synapse  — Nano workers (search, fetch, parse → die)     ║
╚══════════════════════════════════════════════════════════════╝
"""

import os
import gc
import sys
import json
import time
import signal
import logging
import resource
import threading
import traceback
import multiprocessing
from pathlib import Path
from datetime import datetime, timedelta
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, Future, as_completed
from urllib.request import urlopen, Request
from urllib.error import URLError

logger = logging.getLogger("neurons")

# ══════════════════════════════════════════════════════════════
# SYNAPSE — Nano Workers (spawn → fire → die)
# ══════════════════════════════════════════════════════════════

class SynapseResult:
    """Result of a synapse worker execution."""
    __slots__ = ('success', 'data', 'error', 'duration_ms', 'memory_delta_kb', 'worker_id')

    def __init__(self, success: bool, data=None, error=None,
                 duration_ms=0, memory_delta_kb=0, worker_id=""):
        self.success = success
        self.data = data
        self.error = error
        self.duration_ms = duration_ms
        self.memory_delta_kb = memory_delta_kb
        self.worker_id = worker_id

    def to_dict(self):
        return {
            "success": self.success,
            "data": self.data,
            "error": self.error,
            "duration_ms": self.duration_ms,
            "memory_delta_kb": self.memory_delta_kb,
            "worker_id": self.worker_id,
        }


def _synapse_execute(func, args, kwargs, worker_id, timeout):
    """
    Execute a function in an isolated context.
    This runs in a separate PROCESS — when it returns, all memory is freed.
    """
    start_time = time.monotonic()
    mem_before = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

    try:
        # Set alarm for timeout (Unix only)
        if hasattr(signal, 'SIGALRM'):
            def timeout_handler(signum, frame):
                raise TimeoutError(f"Synapse {worker_id} timed out after {timeout}s")
            signal.signal(signal.SIGALRM, timeout_handler)
            signal.alarm(timeout)

        result = func(*args, **kwargs)

        if hasattr(signal, 'SIGALRM'):
            signal.alarm(0)  # Cancel alarm

        duration = (time.monotonic() - start_time) * 1000
        mem_after = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
        mem_delta = mem_after - mem_before

        return SynapseResult(
            success=True,
            data=result,
            duration_ms=round(duration, 1),
            memory_delta_kb=mem_delta,
            worker_id=worker_id,
        )

    except Exception as e:
        duration = (time.monotonic() - start_time) * 1000
        return SynapseResult(
            success=False,
            error=f"{type(e).__name__}: {str(e)}",
            duration_ms=round(duration, 1),
            worker_id=worker_id,
        )


class SynapsePool:
    """
    Pool of nano-workers that spawn, execute, and die.

    Each synapse runs in its own PROCESS — complete memory isolation.
    When the process exits, ALL memory is returned to the OS.
    No memory leaks. No accumulation. Pure spawn-and-die.
    """

    def __init__(self, max_workers: int = 4, default_timeout: int = 30):
        self.max_workers = max_workers
        self.default_timeout = default_timeout
        self.stats = {
            "total_fired": 0,
            "total_success": 0,
            "total_failed": 0,
            "total_duration_ms": 0,
            "active_workers": 0,
        }
        self._lock = threading.Lock()

    def fire(self, func, *args, timeout: int = None,
             worker_id: str = None, **kwargs) -> SynapseResult:
        """
        Fire a synapse: spawn a process, execute, collect result, process dies.

        The function runs in a SEPARATE PROCESS. When done, the entire
        process memory space is reclaimed by the OS. Zero leaks.

        Includes retry logic for Python 3.13 macOS spawn-mode intermittent
        serialization issues ('embedded null character').
        """
        timeout = timeout or self.default_timeout
        worker_id = worker_id or f"syn-{self.stats['total_fired']:04d}"

        with self._lock:
            self.stats["total_fired"] += 1
            self.stats["active_workers"] += 1

        max_retries = 3
        last_error = None

        for attempt in range(max_retries):
            try:
                with ProcessPoolExecutor(max_workers=1) as executor:
                    future = executor.submit(
                        _synapse_execute, func, args, kwargs, worker_id, timeout
                    )
                    result = future.result(timeout=timeout + 5)

                with self._lock:
                    self.stats["active_workers"] -= 1
                    if result.success:
                        self.stats["total_success"] += 1
                    else:
                        self.stats["total_failed"] += 1
                    self.stats["total_duration_ms"] += result.duration_ms

                return result

            except ValueError as e:
                # Python 3.13 macOS spawn-mode bug: "embedded null character"
                last_error = e
                if "embedded null character" in str(e) and attempt < max_retries - 1:
                    import gc; gc.collect()
                    time.sleep(0.1 * (attempt + 1))
                    continue
                break
            except Exception as e:
                last_error = e
                break

        with self._lock:
            self.stats["active_workers"] -= 1
            self.stats["total_failed"] += 1

        return SynapseResult(
            success=False,
            error=f"SynapsePool: {type(last_error).__name__}: {str(last_error)}",
            worker_id=worker_id,
        )

    def fire_parallel(self, tasks: list, timeout: int = None) -> list:
        """
        Fire multiple synapses in parallel. Each in its own process.

        tasks: list of (func, args, kwargs, worker_id) tuples
        Returns: list of SynapseResult

        All processes die after completion — memory fully reclaimed.
        """
        timeout = timeout or self.default_timeout
        results = []

        with ProcessPoolExecutor(max_workers=min(len(tasks), self.max_workers)) as executor:
            futures: dict[Future, str] = {}

            for task in tasks:
                func = task[0]
                args = task[1] if len(task) > 1 else ()
                kwargs = task[2] if len(task) > 2 else {}
                wid = task[3] if len(task) > 3 else f"syn-{self.stats['total_fired']:04d}"

                with self._lock:
                    self.stats["total_fired"] += 1
                    self.stats["active_workers"] += 1

                future = executor.submit(
                    _synapse_execute, func, args, kwargs, wid, timeout
                )
                futures[future] = wid

            for future in as_completed(futures, timeout=timeout + 10):
                wid = futures[future]
                try:
                    result = future.result()
                    results.append(result)
                    with self._lock:
                        self.stats["active_workers"] -= 1
                        if result.success:
                            self.stats["total_success"] += 1
                        else:
                            self.stats["total_failed"] += 1
                        self.stats["total_duration_ms"] += result.duration_ms
                except Exception as e:
                    with self._lock:
                        self.stats["active_workers"] -= 1
                        self.stats["total_failed"] += 1
                    results.append(SynapseResult(
                        success=False,
                        error=str(e),
                        worker_id=wid,
                    ))

        return results

    def get_stats(self) -> dict:
        with self._lock:
            return dict(self.stats)


# ══════════════════════════════════════════════════════════════
# AXON — Lazy-Loading LLM Server (sleeps when idle)
# ══════════════════════════════════════════════════════════════

class Axon:
    """
    The LLM server as a lazy-loading neuron.

    Like a biological axon, it stays dormant until stimulated.
    After a period of inactivity, it unloads (sleep) to free ~4GB RAM.
    When a new request comes, it wakes up (loads model).

    States:
      DORMANT  → Model not loaded, ~0 MB RAM
      LOADING  → Model loading from disk, increasing RAM
      ACTIVE   → Model loaded, serving requests, ~4 GB RAM
      SLEEPING → Transitioning to dormant after idle timeout
    """

    DORMANT = "dormant"
    LOADING = "loading"
    ACTIVE = "active"
    SLEEPING = "sleeping"

    def __init__(self, idle_timeout_minutes: int = 15):
        self.state = self.DORMANT
        self.idle_timeout = timedelta(minutes=idle_timeout_minutes)
        self.last_request_time = None
        self.process = None
        self.load_count = 0
        self.total_requests = 0
        self._lock = threading.Lock()
        self._sleep_timer = None

    def _reset_sleep_timer(self):
        """Reset the idle timer — postpone sleep."""
        if self._sleep_timer:
            self._sleep_timer.cancel()
        self._sleep_timer = threading.Timer(
            self.idle_timeout.total_seconds(),
            self._go_dormant
        )
        self._sleep_timer.daemon = True
        self._sleep_timer.start()

    def _go_dormant(self):
        """Unload model to free RAM."""
        with self._lock:
            if self.state != self.ACTIVE:
                return
            self.state = self.SLEEPING
            logger.info("💤 Axon entering dormant state (freeing ~4GB RAM)...")

            if self.process and self.process.poll() is None:
                self.process.terminate()
                try:
                    self.process.wait(timeout=10)
                except Exception:
                    self.process.kill()
                self.process = None

            # Force garbage collection
            gc.collect()

            self.state = self.DORMANT
            logger.info("💤 Axon dormant — RAM freed")

    def wake(self, cmd: list, port: int, health_url: str,
             timeout: int = 120) -> bool:
        """
        Wake the axon — load the LLM model if dormant.
        If already active, just reset the idle timer.
        Returns True when ready to serve.
        """
        with self._lock:
            self.total_requests += 1
            self.last_request_time = datetime.now()

            if self.state == self.ACTIVE:
                self._reset_sleep_timer()
                return True

            if self.state == self.LOADING:
                # Already loading, wait outside lock
                pass
            else:
                self.state = self.LOADING
                self.load_count += 1
                logger.info(f"⚡ Axon waking up (load #{self.load_count})...")

                try:
                    import subprocess
                    log_path = Path(os.environ.get("MARZIEL_HOME",
                                    Path.home() / "marziel-ai")) / "logs" / "llm.log"
                    log_path.parent.mkdir(parents=True, exist_ok=True)
                    log_file = open(log_path, "a")

                    self.process = subprocess.Popen(
                        cmd,
                        stdout=log_file,
                        stderr=subprocess.STDOUT,
                        env={**os.environ, "PYTHONUNBUFFERED": "1"},
                    )
                except Exception as e:
                    logger.error(f"❌ Axon failed to start: {e}")
                    self.state = self.DORMANT
                    return False

        # Wait for LLM to be ready (outside lock so others can check state)
        for i in range(timeout):
            try:
                resp = urlopen(Request(health_url), timeout=3)
                if resp.status == 200:
                    with self._lock:
                        self.state = self.ACTIVE
                        self._reset_sleep_timer()
                    logger.info(f"⚡ Axon active in {i}s — model loaded")
                    return True
            except Exception:
                pass
            time.sleep(1)

        logger.error("❌ Axon wake timeout — model failed to load")
        with self._lock:
            self.state = self.DORMANT
        return False

    def to_dict(self) -> dict:
        return {
            "state": self.state,
            "load_count": self.load_count,
            "total_requests": self.total_requests,
            "last_request": self.last_request_time.isoformat() if self.last_request_time else None,
            "idle_timeout_min": self.idle_timeout.total_seconds() / 60,
        }


# ══════════════════════════════════════════════════════════════
# DENDRITE — Request Handler (lives for one request cycle)
# ══════════════════════════════════════════════════════════════

class Dendrite:
    """
    A request-scoped handler that coordinates synapses.

    Born when a request arrives, dies when the response is sent.
    Manages parallel synapse workers for context gathering.

    Lifecycle:
      1. Born (request arrives)
      2. Wake axon if needed (ensure LLM is loaded)
      3. Fire synapses for context (web search, GitHub, URL — in parallel)
      4. Collect results
      5. Die (all temp memory freed)
    """

    def __init__(self, request_id: str, synapse_pool: SynapsePool):
        self.request_id = request_id
        self.synapse_pool = synapse_pool
        self.created_at = time.monotonic()
        self.synapse_results = []

    def gather_context(self, message: str, tasks: list) -> list:
        """
        Fire multiple synapse workers to gather context in parallel.
        Each worker runs in its own process and dies when done.

        tasks: list of (func, args, kwargs, worker_id)
        """
        if not tasks:
            return []

        self.synapse_results = self.synapse_pool.fire_parallel(tasks, timeout=15)
        return self.synapse_results

    def get_duration_ms(self) -> float:
        return (time.monotonic() - self.created_at) * 1000

    def __del__(self):
        """Dendrite dies — explicit cleanup."""
        self.synapse_results = None
        gc.collect()


# ══════════════════════════════════════════════════════════════
# NEURAL NETWORK — The complete brain
# ══════════════════════════════════════════════════════════════

class NeuralNetwork:
    """
    The Marziel Neural Network — coordinates all neuron types.

    Architecture:
    ┌───────────────────────────────────────────────┐
    │  NeuralNetwork                                │
    │  ┌─────────┐                                  │
    │  │  Axon   │  LLM Server (lazy-load, sleeps) │
    │  └────┬────┘                                  │
    │       │ wakes on request                      │
    │  ┌────▼────┐                                  │
    │  │Dendrite │  Request handler (born, dies)    │
    │  └────┬────┘                                  │
    │       │ fires in parallel                     │
    │  ┌────▼──────────────────────┐               │
    │  │ Synapse  Synapse  Synapse │  Nano workers  │
    │  │ (search) (github) (url)   │  (spawn, die)  │
    │  └───────────────────────────┘               │
    └───────────────────────────────────────────────┘
    """

    def __init__(self, config: dict = None):
        self.config = config or {}
        self.synapse_pool = SynapsePool(
            max_workers=self.config.get("max_synapse_workers", 4),
            default_timeout=self.config.get("synapse_timeout", 15),
        )
        self.axon = Axon(
            idle_timeout_minutes=self.config.get("idle_timeout_minutes", 15)
        )
        self.dendrite_count = 0
        self._lock = threading.Lock()

    def create_dendrite(self) -> Dendrite:
        """Create a new dendrite for an incoming request."""
        with self._lock:
            self.dendrite_count += 1
            return Dendrite(
                request_id=f"req-{self.dendrite_count:06d}",
                synapse_pool=self.synapse_pool,
            )

    def get_stats(self) -> dict:
        """Get neural network statistics."""
        return {
            "axon": self.axon.to_dict(),
            "synapse_pool": self.synapse_pool.get_stats(),
            "total_dendrites": self.dendrite_count,
        }


# ══════════════════════════════════════════════════════════════
# STANDALONE WORKER FUNCTIONS (run in isolated processes)
# These are the actual "synapse" tasks — they run, return, die.
# ALL web requests use Sentinel for anti-fingerprinting & privacy.
# ══════════════════════════════════════════════════════════════

def _secure_headers(for_api=False):
    """Get anti-fingerprint headers (Sentinel-lite for child processes)."""
    import random
    UAs = [
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/122.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/122.0.0.0 Safari/537.36",
        "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Version/17.3 Safari/605.1.15",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
    ]
    headers = {
        "User-Agent": random.choice(UAs),
        "Accept-Language": random.choice(["en-US,en;q=0.9", "en-GB,en;q=0.9", "en,*;q=0.5"]),
        "DNT": "1",
        "Sec-GPC": "1"
        # NO Referer. NO Cookie. Anti-tracking by default.
    }
    if for_api:
        headers["Accept"] = "application/json"
    else:
        headers["Accept"] = "text/html,application/xhtml+xml,*/*;q=0.8"
    return headers


def _secure_fetch(url, timeout=10, is_api=False):
    """Fetch with anti-fingerprint headers. Secure by default."""
    try:
        headers = _secure_headers(for_api=is_api)
        req = Request(url)
        for k, v in headers.items():
            req.add_header(k, v)
        resp = urlopen(req, timeout=timeout)
        return resp.read().decode("utf-8", errors="ignore")
    except URLError as e:
        # Detect offline status (hostname resolution failed, network down, etc)
        if isinstance(e.reason, OSError) and e.reason.errno in (8, 65, 50, 51, 64):
            raise Exception("SYSTEM_OFFLINE")
        raise


# ── 1. Web Search (secured) ──

def synapse_web_search(query: str) -> dict:
    """Synapse: Secure web search. Anti-fingerprinted. Spawn → fire → die."""
    try:
        import urllib.parse
        encoded = urllib.parse.quote_plus(query)
        url = f"https://api.duckduckgo.com/?q={encoded}&format=json&no_html=1"
        data = json.loads(_secure_fetch(url, is_api=True))
        return {
            "type": "web_search",
            "query": query,
            "abstract": data.get("Abstract", ""),
            "source": data.get("AbstractSource", ""),
            "results": [
                {"text": r.get("Text", ""), "url": r.get("FirstURL", "")}
                for r in data.get("RelatedTopics", [])[:5]
                if isinstance(r, dict) and r.get("Text")
            ],
        }
    except Exception as e:
        return {"type": "web_search", "query": query, "error": str(e)}


# ── 2. URL Fetch (secured) ──

def synapse_fetch_url(url: str) -> dict:
    """Synapse: Secure URL fetch. No tracking. Spawn → fire → die."""
    try:
        content = _secure_fetch(url)[:5000]
        # Basic HTML → text strip
        import re
        text = re.sub(r'<script[^>]*>.*?</script>', '', content, flags=re.DOTALL)
        text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL)
        text = re.sub(r'<[^>]+>', ' ', text)
        text = re.sub(r'\s+', ' ', text).strip()
        return {
            "type": "url_analysis",
            "url": url,
            "content_length": len(text),
            "preview": text[:2000],
        }
    except Exception as e:
        return {"type": "url_analysis", "url": url, "error": str(e)}


# ── 3. GitHub Analysis (secured) ──

def synapse_github_repo(owner: str, repo: str) -> dict:
    """Synapse: Secure GitHub API call. Spawn → fire → die."""
    try:
        api_url = f"https://api.github.com/repos/{owner}/{repo}"
        data = json.loads(_secure_fetch(api_url, is_api=True))

        # Also fetch README
        readme_preview = ""
        try:
            readme_url = f"https://raw.githubusercontent.com/{owner}/{repo}/main/README.md"
            readme = _secure_fetch(readme_url)[:2000]
            readme_preview = readme
        except Exception:
            pass

        return {
            "type": "github_analysis",
            "repo": f"{owner}/{repo}",
            "stars": data.get("stargazers_count", 0),
            "forks": data.get("forks_count", 0),
            "language": data.get("language", ""),
            "description": data.get("description", ""),
            "open_issues": data.get("open_issues_count", 0),
            "license": (data.get("license") or {}).get("spdx_id", "None"),
            "readme_preview": readme_preview[:500],
        }
    except Exception as e:
        return {"type": "github_analysis", "repo": f"{owner}/{repo}", "error": str(e)}


# ── 4. Weather Forecast (NEW — secured) ──

def synapse_weather(lat: float, lon: float, location: str = "Unknown") -> dict:
    """Synapse: Fetch weather + marine forecast. Spawn → fire → die."""
    try:
        w_url = (f"https://api.open-meteo.com/v1/forecast?"
                 f"latitude={lat}&longitude={lon}"
                 f"&daily=temperature_2m_max,temperature_2m_min,wind_speed_10m_max,precipitation_sum"
                 f"&timezone=auto&forecast_days=7")
        m_url = (f"https://marine-api.open-meteo.com/v1/marine?"
                 f"latitude={lat}&longitude={lon}"
                 f"&daily=wave_height_max,wave_period_max"
                 f"&timezone=auto&forecast_days=7")

        weather = json.loads(_secure_fetch(w_url, is_api=True))
        marine = json.loads(_secure_fetch(m_url, is_api=True))

        days = []
        if weather.get("daily") and marine.get("daily"):
            for i in range(7):
                try:
                    days.append({
                        "date": weather["daily"]["time"][i],
                        "temp_min": weather["daily"].get("temperature_2m_min", [0])[i],
                        "temp_max": weather["daily"].get("temperature_2m_max", [0])[i],
                        "wind_max": weather["daily"].get("wind_speed_10m_max", [0])[i],
                        "rain_mm": weather["daily"].get("precipitation_sum", [0])[i],
                        "wave_max": marine["daily"].get("wave_height_max", [0])[i],
                        "wave_period": marine["daily"].get("wave_period_max", [0])[i],
                    })
                except (IndexError, KeyError):
                    pass

        return {
            "type": "weather",
            "location": location,
            "lat": lat, "lon": lon,
            "forecast": days,
        }
    except Exception as e:
        return {"type": "weather", "location": location, "error": str(e)}


# ── 5. Maritime API Call (NEW — for C backend) ──

def synapse_maritime_api(endpoint: str, api_base: str = None) -> dict:
    """Synapse: Call C backend API. Spawn → fire → die."""
    try:
        base = api_base or os.environ.get("C_API_URL", "http://maritime-api:5002")
        url = f"{base}{endpoint}"
        data = json.loads(_secure_fetch(url, is_api=True))
        items = data.get("data", data) if isinstance(data, dict) else data
        return {
            "type": "maritime_api",
            "endpoint": endpoint,
            "count": len(items) if isinstance(items, list) else 1,
            "data": items if isinstance(items, list) else [items],
        }
    except Exception as e:
        return {"type": "maritime_api", "endpoint": endpoint, "error": str(e)}


# ── 6. ML Model Prediction (NEW) ──

def synapse_ml_predict(model_name: str, input_data: dict) -> dict:
    """Synapse: Run ML model prediction. Spawn → fire → die."""
    try:
        # In standalone mode, call the API
        api_url = os.environ.get("MARZIEL_API_URL", "http://localhost:8001")
        import urllib.request
        req_data = json.dumps({"model": model_name, "input": input_data}).encode()
        req = Request(
            f"{api_url}/api/predict/{model_name}",
            data=req_data,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        resp = urlopen(req, timeout=30)
        result = json.loads(resp.read().decode())
        return {
            "type": "ml_predict",
            "model": model_name,
            "prediction": result,
        }
    except Exception as e:
        return {"type": "ml_predict", "model": model_name, "error": str(e)}


# ── 7. Deep Crawl (NEW — multi-page, secured) ──

def synapse_crawl(start_url: str, max_pages: int = 3, max_depth: int = 1) -> dict:
    """Synapse: Deep crawl — follows links on page. Spawn → fire → die."""
    try:
        import re
        import urllib.parse
        visited = set()
        pages = []
        queue = [(start_url, 0)]

        while queue and len(pages) < max_pages:
            url, depth = queue.pop(0)
            if url in visited or depth > max_depth:
                continue
            visited.add(url)

            try:
                content = _secure_fetch(url)
                # Extract text
                text = re.sub(r'<script[^>]*>.*?</script>', '', content, flags=re.DOTALL)
                text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL)
                text = re.sub(r'<[^>]+>', ' ', text)
                text = re.sub(r'\s+', ' ', text).strip()

                pages.append({
                    "url": url,
                    "depth": depth,
                    "text_length": len(text),
                    "preview": text[:1000],
                })

                # Extract links for next depth
                if depth < max_depth:
                    links = re.findall(r'href=["\']([^"\']+)["\']', content)
                    base = urllib.parse.urlparse(url)
                    for link in links[:10]:
                        full_url = urllib.parse.urljoin(url, link)
                        parsed = urllib.parse.urlparse(full_url)
                        if parsed.scheme in ('http', 'https') and parsed.netloc == base.netloc:
                            queue.append((full_url, depth + 1))

            except Exception:
                continue

        return {
            "type": "crawl",
            "start_url": start_url,
            "pages_crawled": len(pages),
            "pages": pages,
        }
    except Exception as e:
        return {"type": "crawl", "start_url": start_url, "error": str(e)}


# ── 8. Sandboxed Code Execution (NEW — high security) ──

def synapse_code_exec(code: str, language: str = "python", timeout: int = 5) -> dict:
    """
    Synapse: Execute code in a sandboxed process. Spawn → fire → die.

    SECURITY:
    - Runs in a SEPARATE PROCESS (memory-isolated by OS)
    - Resource limits: CPU time, memory, no network
    - No file system access outside /tmp
    - Process killed after timeout
    """
    try:
        import subprocess
        import tempfile

        if language != "python":
            return {"type": "code_exec", "error": f"Unsupported language: {language}"}

        # Security: wrap code in resource-limited sandbox
        sandbox_code = f'''
import resource
import sys
import os

# ── SANDBOX RESTRICTIONS ──
# Apply resource limits gracefully (some may not work on all OS)
try:
    resource.setrlimit(resource.RLIMIT_CPU, ({timeout}, {timeout}))
except (ValueError, OSError):
    pass
try:
    resource.setrlimit(resource.RLIMIT_AS, (268435456, 268435456))
except (ValueError, OSError):
    pass  # macOS may not support this
try:
    resource.setrlimit(resource.RLIMIT_FSIZE, (1048576, 1048576))
except (ValueError, OSError):
    pass

# Disable network by removing socket
from importlib.abc import MetaPathFinder
from importlib.machinery import ModuleSpec
_blocked = {{"socket", "http", "urllib", "requests", "subprocess", "shutil"}}
class _ImportBlocker(MetaPathFinder):
    def find_spec(self, fullname, path, target=None):
        if any(fullname == b or fullname.startswith(b + ".") for b in _blocked):
            raise ImportError(f"BLOCKED: {{fullname}} is not allowed in sandbox")
        return None
sys.meta_path.insert(0, _ImportBlocker())

# Change to temp directory
os.chdir("/tmp")

# Execute user code
try:
    exec("""{code.replace(chr(34)*3, chr(92)+chr(34)*3)}""")
except Exception as e:
    print(f"Error: {{type(e).__name__}}: {{e}}", file=sys.stderr)
'''

        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
            f.write(sandbox_code)
            tmp_path = f.name

        try:
            result = subprocess.run(
                [sys.executable, tmp_path],
                capture_output=True,
                text=True,
                timeout=timeout + 2,
                cwd="/tmp",
            )
            return {
                "type": "code_exec",
                "language": language,
                "stdout": result.stdout[:3000],
                "stderr": result.stderr[:1000],
                "exit_code": result.returncode,
            }
        finally:
            try:
                os.unlink(tmp_path)
            except Exception:
                pass

    except Exception as e:
        return {"type": "code_exec", "error": str(e)}


# ── 9. Bash Command Execution (NEW — autonomous agent) ──

def synapse_bash(command: str, timeout: int = 15) -> dict:
    """Synapse: Execute a bash command. Spawn → fire → die."""
    try:
        import subprocess
        # Pre-check for safety
        if "rm -rf /" in command or "mkfs" in command:
            return {"type": "bash", "error": "Unsafe command rejected."}
        
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            text=True,
            timeout=timeout,
            cwd=os.path.expanduser("~"),
        )
        return {
            "type": "bash",
            "command": command,
            "stdout": result.stdout[:4000],
            "stderr": result.stderr[:2000],
            "exit_code": result.returncode,
        }
    except subprocess.TimeoutExpired:
        return {"type": "bash", "command": command, "error": f"Command timed out after {timeout}s"}
    except Exception as e:
        return {"type": "bash", "command": command, "error": str(e)}


# ── 10. File Reading (NEW — autonomous agent) ──

def synapse_file_read(filepath: str, max_lines: int = 500) -> dict:
    """Synapse: Read lines from a local file. Spawn → fire → die."""
    try:
        path = Path(os.path.expanduser(filepath)).resolve()
        if not path.is_file():
            return {"type": "file_io", "file": str(path), "error": "File not found or is a directory."}
        
        with open(path, 'r', encoding='utf-8', errors='replace') as f:
            lines = f.readlines()
            
        content = "".join(lines[:max_lines])
        return {
            "type": "file_io",
            "file": str(path),
            "action": "read",
            "total_lines": len(lines),
            "content": content,
            "truncated": len(lines) > max_lines,
        }
    except Exception as e:
        return {"type": "file_io", "file": str(filepath), "error": str(e)}


# ── 11. File Writing (NEW — autonomous agent) ──

def synapse_file_write(filepath: str, content: str, mode: str = "w") -> dict:
    """Synapse: Write or append to a local file. Spawn → fire → die."""
    try:
        path = Path(os.path.expanduser(filepath)).resolve()
        
        path.parent.mkdir(parents=True, exist_ok=True)
        
        write_mode = "a" if mode == "a" else "w"
        with open(path, write_mode, encoding='utf-8') as f:
            f.write(content)
            
        return {
            "type": "file_io",
            "file": str(path),
            "action": "write",
            "mode": write_mode,
            "bytes_written": len(content.encode('utf-8'))
        }
    except Exception as e:
        return {"type": "file_io", "file": str(filepath), "error": str(e)}


# ══════════════════════════════════════════════════════════════
# CONVENIENCE: Global neural network instance
# ══════════════════════════════════════════════════════════════

_network = None

def get_network(config: dict = None) -> NeuralNetwork:
    """Get or create the global neural network instance."""
    global _network
    if _network is None:
        _network = NeuralNetwork(config)
    return _network
