#!/usr/bin/env python3
"""
Neurotron — Dashboard em modo texto (estilo BIOS)

Ponto de entrada quando executado como:

    python3 -m neurotron

ou via wrapper /usr/bin/neurotron gerado pelo autotools.

Layout:

+------------------------------------------------------------------------------+
| [HEAD]  CPU / MEM / LOAD / UPTIME / MODO / VERSÃO / DIAG                     |
+------------------------------------------------------------------------------+
| [LOG]  Últimos eventos do ciclo cognitivo (observe/think/act/rest, etc.)     |
|        ...                                                                    |
+------------------------------------------------------------------------------+
| [FOOT]  Futuro: input do utilizador (placeholder)  |  teclas: q = sair       |
+------------------------------------------------------------------------------+
"""

import curses
import threading
import time
import os
import sys
from pathlib import Path
from queue import Queue, Empty
from datetime import datetime

# Imports internos do Neurotron (já reorganizados em package)
from .neurotron_config import (
    NEUROTRON_TICK,
    NEUROTRON_MODE,
    NEUROTRON_HOMEOSTASIS,
    NEUROTRON_THRESHOLDS,
)
from .cortex import Cortex
from .autodiagnostic import AutoDiagnostic  # se for usado dentro do Cortex, ok mesmo assim


# =======================================================================================
# Utilitários de ambiente
# =======================================================================================

def detect_persistent_mode():
    """
    Verifica se o hipocampo físico está montado em /var/neurotron.
    Define NEUROTRON_MODE, NEUROTRON_RUNTIME e NEUROTRON_LOG via os.environ.
    """
    mount_point = Path("/var/neurotron")

    def _mounted(mp: Path) -> bool:
        try:
            if mp.exists() and os.path.ismount(mp):
                return True
            with open("/proc/mounts") as f:
                for line in f:
                    if f" {mp} " in line:
                        return True
        except Exception:
            return False
        return False

    if _mounted(mount_point):
        os.environ["NEUROTRON_MODE"] = "persistent"
        os.environ["NEUROTRON_RUNTIME"] = "/var/neurotron/data"
        os.environ["NEUROTRON_LOG"] = "/var/neurotron/logs"
    else:
        os.environ["NEUROTRON_MODE"] = "volatile"
        os.environ["NEUROTRON_RUNTIME"] = "/tmp/neurotron_data"
        os.environ["NEUROTRON_LOG"] = "/tmp/neurotron_logs"

    runtime_dir = Path(os.environ["NEUROTRON_RUNTIME"])
    log_dir = Path(os.environ["NEUROTRON_LOG"])
    runtime_dir.mkdir(parents=True, exist_ok=True)
    log_dir.mkdir(parents=True, exist_ok=True)
    return runtime_dir, log_dir


def read_system_metrics():
    """
    Lê CPU, memória e loadavg a partir de /proc.
    Retorna dicionário:
        {
            "cpu": float,
            "mem": float,
            "load1": float,
            "load5": float,
            "load15": float,
        }
    Em caso de falha, devolve valores -1.
    """
    cpu = -1.0
    mem = -1.0
    load1 = load5 = load15 = -1.0

    # CPU (uso aproximado entre duas leituras de /proc/stat)
    try:
        with open("/proc/stat") as f:
            line = f.readline()
        parts = line.strip().split()
        if parts[0] == "cpu" and len(parts) >= 5:
            user, nice, system_, idle = map(int, parts[1:5])
            total1 = user + nice + system_ + idle
            idle1 = idle
            time.sleep(0.05)  # pequena janela
            with open("/proc/stat") as f:
                line = f.readline()
            parts = line.strip().split()
            user2, nice2, system2, idle2 = map(int, parts[1:5])
            total2 = user2 + nice2 + system2 + idle2
            idle2 = idle2
            total_delta = total2 - total1
            idle_delta = idle2 - idle1
            if total_delta > 0:
                cpu = 100.0 * (1.0 - (idle_delta / total_delta))
    except Exception:
        pass

    # Memória
    try:
        meminfo = {}
        with open("/proc/meminfo") as f:
            for line in f:
                k, v = line.split(":", 1)
                meminfo[k.strip()] = v.strip()
        total_kb = float(meminfo.get("MemTotal", "0 kB").split()[0])
        free_kb = float(meminfo.get("MemAvailable", "0 kB").split()[0])
        if total_kb > 0:
            used_kb = total_kb - free_kb
            mem = 100.0 * (used_kb / total_kb)
    except Exception:
        pass

    # Loadavg
    try:
        with open("/proc/loadavg") as f:
            l1, l5, l15, *_ = f.read().split()
        load1 = float(l1)
        load5 = float(l5)
        load15 = float(l15)
    except Exception:
        pass

    return {
        "cpu": cpu,
        "mem": mem,
        "load1": load1,
        "load5": load5,
        "load15": load15,
    }


# =======================================================================================
# Dashboard em curses
# =======================================================================================

class NeurotronDashboard:
    """
    UI fixa em curses:
      - header: métricas de sistema + estado do Neurotron
      - middle: log rolling
      - footer: placeholder + ajuda/teclas
    """

    def __init__(self, stdscr, log_queue: Queue, cortex: Cortex, start_time: float):
        self.stdscr = stdscr
        self.log_queue = log_queue
        self.cortex = cortex
        self.start_time = start_time

        self.log_lines = []  # mantém histórico para a janela central
        self.max_log_lines = 1000

        self.stop_event = threading.Event()
        self.last_diag_state = "?"
        self.last_diag_delta = "?"

        # tenta obter diagnóstico inicial, se existir
        try:
            diag = self.cortex.diagnostic._load_previous()
            self.last_diag_state = diag.get("state", "?")
            self.last_diag_delta = diag.get("delta", "?")
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Helpers de desenho
    # ------------------------------------------------------------------

    def _draw_header(self, height, width):
        """Desenha a barra superior com CPU/MEM/LOAD/UPTIME/MODO/DIAG."""
        metrics = read_system_metrics()
        now = time.time()
        uptime_sec = int(now - self.start_time)

        hours = uptime_sec // 3600
        mins = (uptime_sec % 3600) // 60
        secs = uptime_sec % 60

        mode = os.environ.get("NEUROTRON_MODE", NEUROTRON_MODE)
        version = getattr(self.cortex, "version", "0.1")

        cpu = metrics["cpu"]
        mem = metrics["mem"]
        load1 = metrics["load1"]

        # Avalia homeostase
        warn_cpu = NEUROTRON_THRESHOLDS.get("cpu_high", 85.0)
        warn_mem = NEUROTRON_THRESHOLDS.get("mem_high", 90.0)
        warn_load = NEUROTRON_THRESHOLDS.get("load1_high", 2.0)

        status_parts = []

        if cpu >= 0:
            status_parts.append(f"CPU: {cpu:5.1f}%")
        else:
            status_parts.append("CPU:  N/A ")

        if mem >= 0:
            status_parts.append(f"MEM: {mem:5.1f}%")
        else:
            status_parts.append("MEM:  N/A ")

        if load1 >= 0:
            status_parts.append(f"LOAD1: {load1:4.2f}")
        else:
            status_parts.append("LOAD1: N/A ")

        status_parts.append(f"UP: {hours:02d}:{mins:02d}:{secs:02d}")
        status_parts.append(f"MODO: {mode.upper()}")
        status_parts.append(f"VER: {version}")
        status_parts.append(f"DIAG: {self.last_diag_state}/{self.last_diag_delta}")

        line = "  ".join(status_parts)

        # barra horizontal
        self.stdscr.attron(curses.A_REVERSE)
        self.stdscr.addnstr(0, 0, line.ljust(width), width)
        self.stdscr.attroff(curses.A_REVERSE)

        # segunda linha: homeostase textual
        homeo_msg = "HOMEOSTASE: OK"
        if (cpu >= warn_cpu and cpu >= 0) or (mem >= warn_mem and mem >= 0) or (
            load1 >= warn_load and load1 >= 0
        ):
            homeo_msg = "HOMEOSTASE: STRESS"

        self.stdscr.addnstr(1, 0, homeo_msg.ljust(width), width)

    def _drain_log_queue(self):
        """Move mensagens da queue para o buffer de linhas."""
        try:
            while True:
                msg = self.log_queue.get_nowait()
                timestamp = datetime.now().strftime("%H:%M:%S")
                self.log_lines.append(f"[{timestamp}] {msg}")
                if len(self.log_lines) > self.max_log_lines:
                    self.log_lines = self.log_lines[-self.max_log_lines:]
        except Empty:
            pass

    def _draw_log_window(self, header_height, footer_height, width, height):
        """
        Área central de logs: rolagem automática, sempre mostrando as últimas N linhas
        que cabem na janela.
        """
        top = header_height
        bottom = height - footer_height
        rows = max(0, bottom - top)

        self._drain_log_queue()

        # seleciona as últimas 'rows' linhas
        visible = self.log_lines[-rows:] if rows > 0 else []

        for i in range(rows):
            y = top + i
            if i < len(visible):
                line = visible[i]
                self.stdscr.addnstr(y, 0, line.ljust(width), width)
            else:
                self.stdscr.addnstr(y, 0, " ".ljust(width), width)

    def _draw_footer(self, width, height):
        """
        Rodapé com placeholder para input futuro e legenda de teclas.
        Protegido contra terminais muito pequenos.
        """
        footer_text = "[ Futuro: comandos do utilizador aparecerão aqui ]"
        keys_text = "[q] sair  |  dashboard Neurotron"

        lines = [footer_text, keys_text]
        footer_lines = len(lines)

        # Se o terminal for demasiado pequeno, encolhe ou só mostra o essencial
        start_row = max(0, height - footer_lines)

        for i, text in enumerate(lines):
            y = start_row + i
            if 0 <= y < height:
                try:
                    self.stdscr.addnstr(y, 0, text.ljust(width), width)
                except curses.error:
                    # Em última instância, ignoramos erros de desenho
                    pass


    # ------------------------------------------------------------------
    # Loop principal da UI
    # ------------------------------------------------------------------

    def run(self):
        """
        Loop principal do curses.
        Actualiza o ecrã, lê teclas, e encerra quando stop_event é setado ou 'q' é pressionado.
        """
        curses.curs_set(0)
        self.stdscr.nodelay(True)
        self.stdscr.keypad(True)

        while not self.stop_event.is_set():
            height, width = self.stdscr.getmaxyx()
            self.stdscr.erase()

            header_height = 2
            footer_height = 2

            try:
                self._draw_header(height, width)
                self._draw_log_window(header_height, footer_height, width, height)
                self._draw_footer(width, height)
            except curses.error:
                # Em terminais muito pequenos ou estados estranhos, evitamos crash
                pass

            self.stdscr.refresh()

            try:
                ch = self.stdscr.getch()
                if ch in (ord("q"), ord("Q")):
                    self.stop_event.set()
                    break
            except Exception:
                pass

            time.sleep(0.1)  # ~10 FPS

        # tentativa graciosa de shutdown do Cortex
        try:
            self.log_queue.put("Encerrando Neurotron (dashboard pediu saída)…")
            self.cortex.shutdown(reason="Dashboard exit")
        except Exception:
            pass


# =======================================================================================
# Ciclo Cognitivo em thread separada
# =======================================================================================

def cognitive_loop(cortex: Cortex, ui: NeurotronDashboard):
    """
    Loop cognitivo clássico (observe → think → act → rest),
    a correr numa thread separada, reportando eventos para o dashboard via log_queue.
    """
    log = ui.log_queue.put

    try:
        log("Neurotron: boot()…")
        cortex.boot()
        try:
            state = cortex.diagnostic._load_previous().get("state", "?")
            log(f"Diagnóstico inicial: estado='{state}'")
            ui.last_diag_state = state
        except Exception:
            log("Diagnóstico inicial: indisponível")

        log("Ciclo cognitivo iniciado (observe → think → act → rest)…")

        while not ui.stop_event.is_set():
            try:
                log("cortex.observe()")
                cortex.observe()

                log("cortex.think()")
                cortex.think()

                log("cortex.act()")
                cortex.act()

                log("cortex.rest()")
                cortex.rest()

            except KeyboardInterrupt:
                log("Interrompido pelo utilizador (SIGINT)")
                cortex.shutdown(reason="SIGINT")
                break
            except SystemExit:
                log("SystemExit recebido, encerrando…")
                cortex.shutdown(reason="SystemExit")
                break
            except Exception as e:
                log(f"💥 Exceção não tratada no loop cognitivo: {e}")
                try:
                    cortex.fatal(e)
                finally:
                    ui.stop_event.set()

    finally:
        ui.stop_event.set()
        log("Loop cognitivo terminado.")


# =======================================================================================
# Entry point
# =======================================================================================

def _main_curses(stdscr):
    # 1) Detecta modo e diretórios
    runtime_dir, log_dir = detect_persistent_mode()

    # 2) Inicializa Cortex com os mesmos parâmetros do main_waiting
    cortex = Cortex(
        runtime_dir=runtime_dir,
        log_dir=log_dir,
        tick_seconds=NEUROTRON_TICK,
    )

    # 3) Queue de logs e dashboard
    log_queue: Queue = Queue()
    start_time = time.time()
    ui = NeurotronDashboard(stdscr, log_queue, cortex, start_time)

    # 4) Thread do ciclo cognitivo
    worker = threading.Thread(
        target=cognitive_loop,
        args=(cortex, ui),
        daemon=True,
    )
    worker.start()

    # 5) Loop da UI (bloqueia até terminar)
    ui.run()

    # 6) Aguarda thread terminar
    worker.join(timeout=2.0)


def main():
    """
    Ponto de entrada Python. Usado tanto por:

        python3 -m neurotron

    como pelo wrapper /usr/bin/neurotron, se este fizer:

        from neurotron import main
        main()
    """
    curses.wrapper(_main_curses)


if __name__ == "__main__":
    main()
