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

from neurotron.logbus import logbus

from .neuron import Neuron
from .hippocampus import Hippocampus
from .perception import Perception
from .motor import Motor
from .autodiagnostic import AutoDiagnostic

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, HEARTBEAT_STYLE,
    NEUROTRON_THRESHOLDS,
    TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
)


# =====================================================================
# Neurónios básicos
# =====================================================================

class VitalSigns(Neuron):
    name = "VitalSigns"

    def observe(self):
        snap = self.ctx.perception.snapshot()
        self.publish("vitals", snap)
        self.ctx.memory.remember("observe.vitals", snap)


class EchoAgent(Neuron):
    name = "EchoAgent"

    def think(self):
        msg = self.consume("vitals")
        if msg:
            self.publish("actions", {
                "action": "echo",
                "text": f"CPU {msg.get('cpu_percent', '?')}%"
            })


# =====================================================================
# C O R T E X — versão V6 (Pure LogBus Edition)
# =====================================================================

class Cortex:
    """
    Orquestrador cognitivo do Neurotron.
    Totalmente silencioso — toda saída vai para o logbus.
    """

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

        self.tick = float(tick_seconds)
        self.mode = NEUROTRON_MODE
        self._tick_count = 0

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

        # Message bus interno
        self.bus = defaultdict(lambda: deque(maxlen=32))

        # Telemetria curta em RAM
        self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)

        # Neurónios ativos
        self.neurons = [
            VitalSigns(self),
            EchoAgent(self),
        ]

        self._booted = False

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

    # =================================================================
    # BOOT / SHUTDOWN
    # =================================================================

    def boot(self):
        if self._booted:
            return
        logbus.emit("🧠 boot() — inicializando Neurotron")
        self.memory.remember("boot", {"version": "0.1", "tick": self.tick})
        self._booted = True

    def shutdown(self, reason=""):
        logbus.emit(f"⚠️ shutdown — {reason}")
        self.memory.remember("shutdown", {"reason": reason})

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

    # =================================================================
    # CICLO COGNITIVO
    # =================================================================

    def observe(self):
        for n in self.neurons:
            n.observe()

    def think(self):
        for n in self.neurons:
            n.think()

    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", "")])
            self.memory.remember("act.echo", res)

            # Redireciona stdout para logbus
            if res.get("stdout"):
                logbus.emit(f"[motor.echo] {res['stdout'].strip()}")

            if res.get("stderr"):
                logbus.emit(f"[motor.echo.err] {res['stderr'].strip()}")

    def rest(self):
        # Heartbeat + microalertas
        if HEARTBEAT_ENABLED:
            self._heartbeat_and_telemetry()

        sleep(self.tick)
        self._tick_count += 1

        # Diagnóstico periódico
        if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
            state, _ = self.diagnostic.run_exam()
            self._apply_homeostasis(state)

        # Flush periódico telemetria
        if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
            self._flush_telemetry()

    # =================================================================
    # HOMEOSTASE
    # =================================================================

    def _apply_homeostasis(self, state):
        old = self.tick

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

        if self.tick != old:
            logbus.emit(f"[homeostasis] tick ajustado {old:.2f}s → {self.tick:.2f}s (state={state})")

    # =================================================================
    # HEARTBEAT + MICROALERTAS
    # =================================================================

    def _heartbeat_and_telemetry(self):
        snap = self.perception.snapshot()

        cpu = snap.get("cpu_percent")
        mem = snap.get("mem_percent")
        load = snap.get("loadavg")
        load1 = load[0] if isinstance(load, list) and load else None

        # Salva telemetria curta
        self.telemetry.append({
            "ts": time.time(),
            "cpu": cpu,
            "mem": mem,
            "load": load,
            "tick": self.tick,
        })

        self._evaluate_microalerts(cpu, mem, load1)

        # Heartbeat → logbus
        logbus.emit(f"💓 cpu={cpu}% mem={mem}% tick={self.tick:.2f}s")

    def _evaluate_microalerts(self, cpu, mem, load1):
        alerts = []

        try:
            if isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]:
                alerts.append(("cpu", cpu))
            if isinstance(mem, (int, float)) and mem >= NEUROTRON_THRESHOLDS["mem_high"]:
                alerts.append(("mem", mem))
            if isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"]:
                alerts.append(("load1", load1))
        except Exception:
            return

        if not alerts:
            return

        for metric, value in alerts:
            logbus.emit(f"⚠️ microalerta: {metric}={value} — ajustando tick")

        self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP)

        self.memory.remember("microalert", {
            "ts": time.time(),
            "alerts": alerts,
            "new_tick": self.tick,
        })

    # =================================================================
    # TELEMETRIA LONGA
    # =================================================================

    def _flush_telemetry(self):
        try:
            data = list(self.telemetry)
            self.telemetry_path.write_text(json.dumps(data))
            self.memory.remember("telemetry.flush", {
                "count": len(data),
                "path": str(self.telemetry_path)
            })
        except Exception as e:
            logbus.emit(f"[telemetry.error] {e}")

    # =================================================================
    # BUS
    # =================================================================

    def bus_publish(self, channel, payload):
        self.bus[channel].append(payload)

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