# neurotron/disk_agent.py
import subprocess
from pathlib import Path

from neurotron.neuron import Neuron
from neurotron.logbus import logbus

MOUNT_POINT = "/mnt/nfdos"


class DiskAgent(Neuron):
    """
    DiskAgent minimalista e pragmático:

    - Descobre o primeiro disco "real" em /sys/block (vda, sda, nvme0n1, etc)
    - Se existir uma partição (ex: /dev/vda1), usa essa.
    - Caso contrário, usa o disco inteiro (ex: /dev/vda).
    - Verifica se já existe filesystem:
        - se sim  → monta
        - se não → mkfs.ext4 e depois monta
    - Ao montar com sucesso:
        - muda ctx.mode → "persistent"
        - cria /mnt/nfdos/{data,logs,dna}
    """

    name = "DiskAgent"

    def __init__(self, ctx):
        super().__init__(ctx)
        self.state = "unknown"   # unknown → found → fs_ok → mounted
        self.cooldown = 0
        self.last_msg = None
        self.dev = None          # disco base (ex: /dev/vda)
        self.target = None       # device efetivo (ex: /dev/vda1 ou /dev/vda)

    # ------------------------------------------------------------------
    # HELPERS
    # ------------------------------------------------------------------

    def _emit_once(self, msg: str):
        if msg != self.last_msg:
            logbus.disk(msg)
            self.last_msg = msg

    def _run_ok(self, cmd: list[str]) -> bool:
        try:
            subprocess.run(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                check=True,
            )
            return True
        except Exception:
            return False

    def _run_capture(self, cmd: list[str]) -> dict:
        """
        Executa comando e captura stdout/stderr + returncode.
        Não levanta exceção, devolve estrutura amigável.
        """
        try:
            proc = subprocess.run(
                cmd,
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            return {
                "ok": (proc.returncode == 0),
                "rc": proc.returncode,
                "out": (proc.stdout or "").strip(),
                "err": (proc.stderr or "").strip(),
            }
        except Exception as e:
            return {
                "ok": False,
                "rc": -1,
                "out": "",
                "err": repr(e),
            }

    def _find_real_disks(self):
        """
        Procura discos reais em /sys/block:
        - aceita: vda, sda, hda, nvme0n1, etc.
        - ignora: loop*, ram*, dm-*, sr*
        Converte para /dev/<name> se existir.
        """
        disks = []
        sysblock = Path("/sys/block")
        if not sysblock.exists():
            return disks

        for dev in sysblock.iterdir():
            name = dev.name
            if name.startswith(("ram", "loop", "dm-", "sr")):
                continue

            path = Path("/dev") / name
            if path.exists():
                disks.append(str(path))

        return disks

    def _pick_target(self, base_dev: str) -> str:
        """
        Escolhe o device real a usar:
        - se existir <dev>1 (ex: /dev/vda1), usa essa partição;
        - caso contrário, usa o disco inteiro.
        """
        # tentar partição "1"
        p1 = f"{base_dev}1"
        if Path(p1).exists():
            return p1

        # se for NVMe, a convenção é ex: /dev/nvme0n1p1
        if "nvme" in base_dev:
            p_nvme = base_dev + "p1"
            if Path(p_nvme).exists():
                return p_nvme

        # fallback → disco inteiro
        return base_dev

    # ------------------------------------------------------------------
    # CICLO
    # ------------------------------------------------------------------

    def observe(self):
        # Evita spam excessivo
        if self.cooldown > 0:
            self.cooldown -= 1
            return
        self.cooldown = 10

        # ESTADO: já montado → nada para fazer
        if self.state == "mounted":
            return

        # 1) Encontrar disco base
        disks = self._find_real_disks()
        if not disks:
            self._emit_once("Nenhum disco de bloco encontrado — VOLATILE")
            self.state = "no_disk"
            return

        dev = disks[0]
        target = self._pick_target(dev)

        self.dev = dev
        self.target = target

        # ESTADO: unknown → found
        if self.state == "unknown":
            if target == dev:
                self._emit_once(f"Disco detectado: {dev} (sem partições visíveis, usando disco inteiro)")
            else:
                self._emit_once(f"Disco detectado: {dev} (usando {target})")
            self.state = "found"
            return

        # 2) Verificar se device existe mesmo (evitar "Can't lookup blockdev")
        if not Path(self.target).exists():
            self._emit_once(f"{self.target}: ainda não existe em /dev — aguardando…")
            self.cooldown = 5
            return

        # 3) ESTADO: found → verificar filesystem
        if self.state == "found":
            res_blkid = self._run_capture(["blkid", self.target])
            blkid_out = res_blkid["out"]

            if "TYPE=" in blkid_out:
                self._emit_once("Filesystem existente detectado")
                self.state = "fs_ok"
                return

            # Não há TYPE= → precisamos formatar
            self._emit_once("Formatação filesystem (mke2fs) — journaling ON")

            # BusyBox mke2fs não aceita -t, e o e2fsprogs aceita.
            cmd = ["/sbin/mke2fs", "-t", "ext4", "-F", self.target]
            res_mkfs = self._run_capture(cmd)

            if not res_mkfs["ok"]:
                logbus.error(f"[mkfs] Falhou (rc={res_mkfs['rc']})")
                if res_mkfs["err"]:
                    logbus.error(f"[mkfs.err] {res_mkfs['err']}")
                if res_mkfs["out"]:
                    logbus.error(f"[mkfs.out] {res_mkfs['out']}")
                self._emit_once("Erro na formatação — retry mais tarde")
                self.cooldown = 20
                return

            self._emit_once("Filesystem criado (mke2fs OK) 🎉")
            self.state = "fs_ok"
            return

        # 4) ESTADO: fs_ok → montar
        if self.state == "fs_ok":
            Path(MOUNT_POINT).mkdir(parents=True, exist_ok=True)

            # --- montar FS ---
            opts = "rw,noatime"
            cmd = ["mount", "-o", opts, self.target, MOUNT_POINT]
            res_mount = self._run_capture(cmd)

            if res_mount["ok"]:
                self._emit_once(f"Montado em {MOUNT_POINT} ({self.target})")
                self.state = "mounted"

                if self.ctx.mode != "persistent":
                    self.ctx.mode = "persistent"
                    logbus.info("Modo alterado → PERSISTENT")

                for d in ["data", "logs", "dna"]:
                    Path(MOUNT_POINT, d).mkdir(exist_ok=True)

            else:
                logbus.error(f"[mount] Falhou (rc={res_mount['rc']})")
                if res_mount["err"]:
                    logbus.error(f"[mount.err] {res_mount['err']}")
                if res_mount["out"]:
                    logbus.error(f"[mount.out] {res_mount['out']}")
                self._emit_once("Falha ao montar — retry mais tarde")
                self.cooldown = 15

            return
