# neurotron/cortex.py

import json
import time
from pathlib import Path
from collections import defaultdict, deque
from time import sleep

from neurotron.logbus import logbus
from neurotron.disk_agent import DiskAgent
from neurotron.vitalsigns_agent import VitalSigns
from neurotron.echo_agent import EchoAgent

from .hippocampus import Hippocampus
from .perception import Perception
from .motor import Motor
from .autodiagnostic import AutoDiagnostic
from .telemetry import TelemetryV5   # <-- NOVO

from .neurotron_config import (
    NEUROTRON_MODE, NEUROTRON_TICK,
    NEUROTRON_TICK_MIN, NEUROTRON_TICK_MAX, NEUROTRON_TICK_STEP,
    NEUROTRON_DIAG_EVERY_TICKS,
    NEUROTRON_DATASET_PATH,
    HEARTBEAT_ENABLED,
    TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
)

from .trm import TRMEngine   # depois dos outros imports internos

class Cortex:
    def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK):
        self.runtime_dir = Path(runtime_dir)
        self.log_dir = Path(log_dir)

        try:
            self.tick = float(tick_seconds)
        except:
            self.tick = 1.0

        self.mode = NEUROTRON_MODE
        self._tick_count = 0

        self.diagnostic = AutoDiagnostic(runtime_dir, log_dir)
        self.memory = Hippocampus(log_dir=self.log_dir)
        self.perception = Perception()
        self.motor = Motor()

        self.bus = defaultdict(lambda: deque(maxlen=32))
        self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)

        self.neurons = [
            DiskAgent(self),
            VitalSigns(self),
            EchoAgent(self),
        ]

        # ---- Telemetria V5 ----
        self.tele = TelemetryV5(event_callback=self._telemetry_event)

        # ---- TRM v1 ----
        try:
            self.trm = TRMEngine(cortex=self)
        except Exception as e:
            logbus.debug(f"[trm.engine] falha ao inicializar TRMEngine: {e}")
            self.trm = None

        self._booted = False

        self.telemetry_path = Path(NEUROTRON_DATASET_PATH) / "telemetry.json"
        self.telemetry_path.parent.mkdir(parents=True, exist_ok=True)

    # ----------------------------------------
    # boot
    # ----------------------------------------
    def boot(self):
        if self._booted:
            return
        logbus.info(f"Neurotron boot() — mode={self.mode}")
        self.memory.remember("boot", {"tick": self.tick, "mode": self.mode})
        self._booted = True

    # ----------------------------------------
    # shutdown
    # ----------------------------------------
    def shutdown(self, reason=""):
        logbus.warn(f"Shutdown pedido — {reason}")
        self.memory.remember("shutdown", {"reason": reason})

    def fatal(self, e: Exception):
        logbus.error(f"Fatal: {repr(e)}")
        self.memory.remember("fatal", {"error": repr(e)})
        raise e

    # ----------------------------------------
    # ciclo cognitivo
    # ----------------------------------------
    def observe(self):
        for n in self.neurons:
            try:
                n.observe()
            except Exception as e:
                logbus.error(f"{n.name}.observe: {e}")

    def think(self):
        for n in self.neurons:
            try:
                n.think()
            except Exception as e:
                logbus.error(f"{n.name}.think: {e}")

    def act(self):
        action = self.bus_consume("actions")
        if not action:
            return

        if action.get("action") == "echo":
            res = self.motor.run("echo", [action.get("text", "")])
            msg = res.get("stdout", "").strip()
            if msg:
                logbus.info(f"[echo] {msg}")

    def rest(self):
        # ------- Telemetria V5 ------
        try:
            tele = self.tele.step()
            self.telemetry.append(tele)
        except Exception as e:
            logbus.error(f"telemetry_step: {e}")

        # ------- TRM v1 (pensamento interno simbólico) ------
        if self.trm and tele is not None:
            try:
                self.trm.step(tele)
            except Exception as e:
                logbus.debug(f"[trm.engine] step falhou: {e}")

        # ------- Heartbeat visual ------
        if HEARTBEAT_ENABLED:
            self._heartbeat()

        # ------- Tick ------
        sleep(self._safe_float(self.tick, fallback=1.0))
        self._tick_count += 1

        # ------- Diag -------
        if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
            self._run_diag()

        # ------- Persistência -------
        if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
            self._flush_telemetry()

    # ----------------------------------------
    # helpers
    # ----------------------------------------
    @staticmethod
    def _safe_float(x, fallback=0.0):
        try:
            return float(x)
        except:
            return fallback

    # ----------------------------------------
    # heartbeat — apenas visual, não cognitivo
    # ----------------------------------------
    def _heartbeat(self):
        snap = self.perception.snapshot()

        cpu = self._safe_float(snap.get("cpu_percent"), 0.0)
        mem = self._safe_float(snap.get("mem_percent"), 0.0)
        load = snap.get("loadavg")
        load1 = self._safe_float(load[0] if load else 0.0, 0.0)

        try:
            logbus.heart(f"cpu={cpu}% mem={mem}% tick={self.tick:.2f}s")
        except:
            logbus.heart(f"cpu={cpu}% mem={mem}% tick={self.tick}")

    # ----------------------------------------
    # telemetria persistência
    # ----------------------------------------
    def _flush_telemetry(self):
        try:
            data = list(self.telemetry)
            self.telemetry_path.write_text(json.dumps(data))
            self.memory.remember("telemetry.flush", {})
        except Exception as e:
            logbus.error(f"telemetry_flush: {e}")

    # ----------------------------------------
    # diag + homeostase
    # ----------------------------------------
    def _run_diag(self):
        state, snap = self.diagnostic.run_exam()

        cpu = snap.get("cpu")
        mem = snap.get("mem")
        load1 = snap.get("load1")

        logbus.diag(f"estado={state} cpu={cpu} mem={mem} load1={load1}")

        old = self._safe_float(self.tick, 1.0)

        if state == "CRITICAL":
            self.tick = min(NEUROTRON_TICK_MAX, old + NEUROTRON_TICK_STEP)
        elif state == "ALERT":
            self.tick = min(NEUROTRON_TICK_MAX, old + NEUROTRON_TICK_STEP / 2)
        elif state == "STABLE":
            self.tick = max(NEUROTRON_TICK_MIN, old - NEUROTRON_TICK_STEP / 2)

        if self.tick != old:
            logbus.info(f"tick ajustado {old:.2f}s → {self.tick:.2f}s")

    # ----------------------------------------
    # callback eventos telemétricos (Hippocampus)
    # ----------------------------------------
    def _telemetry_event(self, event_name, payload):
        self.memory.remember(f"telemetry.event.{event_name}", payload)

    # ----------------------------------------
    # bus interno
    # ----------------------------------------
    def bus_publish(self, ch, payload):
        self.bus[ch].append(payload)

    def bus_consume(self, ch):
        q = self.bus[ch]
        return q.popleft() if q else None

