import json
import time
from collections import defaultdict, deque
from pathlib import Path
from time import sleep
from rich.console import Console

from neuron import Neuron
from hippocampus import Hippocampus
from perception import Perception
from motor import Motor

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,
)
from .autodiagnostic import AutoDiagnostic


class VitalSigns(Neuron):
    name = "VitalSigns"
    def observe(self) -> None:
        snap = self.ctx.perception.snapshot()
        self.publish("vitals", snap)
        self.ctx.memory.remember("observe.vitals", snap)


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


class Cortex:
    """
    Orquestrador: liga neurónios, bus de mensagens, memória, IO e ciclo cognitivo.
    Agora com Telemetria Contínua (V5): heartbeat, microalertas e flush periódico.
    """
    def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK):
        self.runtime_dir = runtime_dir
        self.log_dir = log_dir
        self.tick = float(tick_seconds)
        self.mode = NEUROTRON_MODE
        self._tick_count = 0
        self.diagnostic = AutoDiagnostic(runtime_dir, log_dir)

        self.console = Console()
        self.memory = Hippocampus(log_dir=log_dir)
        self.perception = Perception()
        self.motor = Motor()

        # Message bus simples: channels → deque
        self.bus = defaultdict(lambda: deque(maxlen=32))

        # Telemetria em memória (curto prazo)
        self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)

        # Regista neurónios (podes adicionar mais à medida)
        self.neurons: list[Neuron] = [
            VitalSigns(self),
            EchoAgent(self),
        ]

        self._booted = False

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

    # ——— ciclo de vida ———
    def boot(self) -> None:
        if self._booted:
            return
        self.console.print("[bold cyan]🧠 Neurotron[/] — boot")
        self.memory.remember("boot", {"version": "0.1", "tick": self.tick})
        self._booted = True
        state, _ = self.diagnostic.run_exam()
        self._apply_homeostasis(state)

    def _apply_homeostasis(self, state):
        if state == "CRITICAL":
            self.mode = "diagnostic"
            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)
        # UNKNOWN → não mexe

    def shutdown(self, reason: str = ""):
        self.console.print(f"[yellow]shutdown:[/] {reason}")
        self.memory.remember("shutdown", {"reason": reason})

    def fatal(self, e: Exception):
        self.console.print(f"[red]fatal:[/] {e!r}")
        self.memory.remember("fatal", {"error": repr(e)})
        print(f"fatal: {repr(e)}")
        raise

    # ——— loop ———
    def observe(self) -> None:
        for n in self.neurons:
            n.observe()

    def think(self) -> None:
        for n in self.neurons:
            n.think()

    def act(self) -> None:
        # Consumir ações agregadas e executar
        action = self.bus_consume("actions")
        if action and action.get("action") == "echo":
            res = self.motor.run("echo", [action.get("text", "")])
            self.memory.remember("act.echo", res)
            if res.get("stdout"):
                self.console.print(f"[green]{res['stdout'].strip()}[/]")

    def rest(self):
        # Heartbeat e microalertas antes de dormir
        if HEARTBEAT_ENABLED:
            self._heartbeat_and_telemetry()

        # Pausa regulada
        sleep(self.tick)

        # Contador e rotinas periódicas
        self._tick_count += 1

        if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
            state, _ = self.diagnostic.run_exam()
            self._apply_homeostasis(state)

        if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
            self._flush_telemetry()

    # ——— telemetria/alertas ———
    def _heartbeat_and_telemetry(self):
        snap = self.perception.snapshot()
        cpu = snap.get("cpu_percent", "?")
        mem = (snap.get("mem") or {}).get("percent", "?")
        load = snap.get("loadavg") or []

        # Adiciona ao buffer de telemetria
        self.telemetry.append({
            "ts": time.time(),
            "cpu": cpu,
            "mem": mem,
            "load": load,
            "tick": self.tick,
        })

        # Microalertas com base nos limiares
        self._evaluate_microalerts(cpu, mem, load)

        # Heartbeat visual
        color = self._color_for_levels(cpu, mem, load)
        if HEARTBEAT_STYLE == "compact":
            self.console.print(f"[bold {color}]💓[/] CPU: {cpu}% | MEM: {mem}% | TICK: {self.tick:.2f}s")
        else:
            self.console.print(
                f"[bold {color}]💓 [Heartbeat][/bold {color}] "
                f"CPU: {cpu}% | MEM: {mem}% | LOAD: {load} | TICK: {self.tick:.2f}s | MODE: {self.mode}"
            )

    def _evaluate_microalerts(self, cpu, mem, load):
        alerts = []
        # Normaliza
        load1 = load[0] if (isinstance(load, (list, tuple)) and load) else None

        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 KeyError:
            pass  # thresholds incompletos → sem microalertas

        if not alerts:
            return

        for (metric, value) in alerts:
            self.console.print(f"[yellow]⚠️ Microalerta:[/] {metric.upper()} {value} — ajustando homeostase (tick +{NEUROTRON_TICK_STEP:.2f}s)")
        # Ajuste simples de segurança
        self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP)

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

    def _color_for_levels(self, cpu, mem, load):
        # Heurística simples de cor
        try:
            load1 = load[0] if (isinstance(load, (list, tuple)) and load) else 0.0
            high = (
                (isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]) or
                (isinstance(mem, (int, float)) and mem >= NEUROTRON_THRESHOLDS["mem_high"]) or
                (isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"])
            )
            if high:
                return "yellow"
        except Exception:
            pass
        return "green"

    def _flush_telemetry(self):
        # Grava o buffer de telemetria em JSON (mantendo histórico curto)
        try:
            data = list(self.telemetry)
            with self.telemetry_path.open("w") as f:
                json.dump(data, f)
            self.memory.remember("telemetry.flush", {"count": len(data), "path": str(self.telemetry_path)})
        except Exception as e:
            self.console.print(f"[red]✖ Falha ao gravar telemetria:[/] {e!r}")
            self.memory.remember("telemetry.error", {"error": repr(e)})

    # ——— bus ———
    def bus_publish(self, channel: str, payload: dict) -> None:
        self.bus[channel].append(payload)

    def bus_consume(self, channel: str) -> dict | None:
        q = self.bus[channel]
        return q.popleft() if q else None