"""MD OS runtime — system workloads, apps, programs (v0.3)."""
from __future__ import annotations

import json
import os
import platform
import shutil
import socket
import subprocess
import sys
import threading
import time
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional

CYN = "Cyn"


class OsCynError(Exception):
    pass


def _s(v: Any) -> str:
    if v is None:
        return ""
    if isinstance(v, float) and v.is_integer():
        return str(int(v))
    return str(v)


# 14 OS show-word constants (items 54–67 of the +67 set)
OS_WORDS: Dict[str, str] = {
    "CopperOS": "Copper OS",
    "Kernel": "Copper 9 Kernel",
    "Shell": "Maintenance Shell",
    "Process": "Worker Process",
    "Daemon": "Background Daemon",
    "Filesystem": "Hive Filesystem",
    "Bootloader": "Absolute Bootloader",
    "Syscall": "Solver Syscall",
    "Scheduler": "Drone Scheduler",
    "Mutex": "Hive Mutex",
    "Watchdog": "Cyn Watchdog",
    "Firmware": "Drone Firmware",
    "Mount": "Filesystem Mount",
    "WorkerProcess": "Worker Drone Process",
}


class OsRuntime:
    def __init__(self, base_path: str):
        self.base_path = base_path
        self.registry: Dict[str, Dict[str, Any]] = {}
        self.queues: Dict[str, List[Any]] = {}
        self._procs: List[subprocess.Popen] = []

    def set_base(self, path: str) -> None:
        self.base_path = path

    def _path(self, p: Any) -> str:
        s = _s(p)
        return s if os.path.isabs(s) else os.path.normpath(os.path.join(self.base_path, s))

    def register_unit(self, name: str, unit_type: str, fn: Callable, meta: Optional[dict] = None) -> None:
        self.registry[name] = {
            "type": unit_type,
            "fn": fn,
            "running": False,
            "meta": meta or {},
        }

    def deploy(self, name: str) -> Any:
        if name not in self.registry:
            raise OsCynError(f"Unit '{name}' not in OS registry — Cyn denies deploy.")
        unit = self.registry[name]
        unit["running"] = True
        return unit["fn"]()

    def launch(self, name: str, *args: Any) -> Any:
        if name not in self.registry:
            raise OsCynError(f"Cannot launch '{name}' — not installed.")
        unit = self.registry[name]
        unit["running"] = True
        return unit["fn"](*args)

    def halt(self, name: str) -> None:
        if name in self.registry:
            self.registry[name]["running"] = False

    def status(self, name: str) -> str:
        if name not in self.registry:
            return "missing"
        return "running" if self.registry[name]["running"] else "halted"

    def install_unit(self, name: str, unit_type: str, path: str) -> None:
        full = self._path(path)
        self.registry[name] = {
            "type": unit_type,
            "fn": lambda: self.boot(f"python3 {full}"),
            "running": False,
            "meta": {path_key: full for path_key in ("path",)},
        }

    def uninstall(self, name: str) -> None:
        if name in self.registry:
            del self.registry[name]

    # --- filesystem & shell (22 builtins: items 1–22) ---

    def boot(self, cmd: Any) -> str:
        r = subprocess.run(_s(cmd), shell=True, capture_output=True, text=True, cwd=self.base_path)
        if r.returncode != 0 and r.stderr:
            raise OsCynError(f"boot failed: {r.stderr.strip()}")
        return r.stdout.strip()

    def exec_code(self, cmd: Any) -> int:
        return subprocess.run(_s(cmd), shell=True, cwd=self.base_path).returncode

    def fork_proc(self, cmd: Any) -> int:
        p = subprocess.Popen(_s(cmd), shell=True, cwd=self.base_path)
        self._procs.append(p)
        return p.pid

    def getenv(self, key: Any) -> str:
        return os.environ.get(_s(key), "")

    def setenv(self, key: Any, val: Any) -> None:
        os.environ[_s(key)] = _s(val)

    def pwd(self) -> str:
        return os.getcwd()

    def cd(self, path: Any) -> str:
        os.chdir(self._path(path))
        return os.getcwd()

    def ls(self, path: Any = ".") -> List[str]:
        return sorted(os.listdir(self._path(path)))

    def read(self, path: Any) -> str:
        with open(self._path(path), "r", encoding="utf-8") as f:
            return f.read()

    def write(self, path: Any, data: Any) -> str:
        full = self._path(path)
        os.makedirs(os.path.dirname(full) or ".", exist_ok=True)
        with open(full, "w", encoding="utf-8") as f:
            f.write(_s(data))
        return full

    def append(self, path: Any, data: Any) -> str:
        with open(self._path(path), "a", encoding="utf-8") as f:
            f.write(_s(data))
        return self._path(path)

    def erase(self, path: Any) -> bool:
        full = self._path(path)
        if os.path.isdir(full):
            shutil.rmtree(full)
        elif os.path.isfile(full):
            os.remove(full)
        else:
            return False
        return True

    def exist(self, path: Any) -> bool:
        return os.path.exists(self._path(path))

    def mkdir(self, path: Any) -> str:
        full = self._path(path)
        os.makedirs(full, exist_ok=True)
        return full

    def rmdir(self, path: Any) -> bool:
        full = self._path(path)
        if os.path.isdir(full):
            os.rmdir(full)
            return True
        return False

    def copy(self, src: Any, dst: Any) -> str:
        s, d = self._path(src), self._path(dst)
        if os.path.isdir(s):
            shutil.copytree(s, d, dirs_exist_ok=True)
        else:
            shutil.copy2(s, d)
        return d

    def move(self, src: Any, dst: Any) -> str:
        shutil.move(self._path(src), self._path(dst))
        return self._path(dst)

    def stat(self, path: Any) -> Dict[str, Any]:
        full = self._path(path)
        st = os.stat(full)
        return {
            "path": full,
            "size": st.st_size,
            "is_file": os.path.isfile(full),
            "is_dir": os.path.isdir(full),
            "mtime": st.st_mtime,
        }

    def chmod(self, path: Any, mode: Any) -> None:
        os.chmod(self._path(path), int(mode))

    def which(self, cmd: Any) -> str:
        from shutil import which
        return which(_s(cmd)) or ""

    def pipe_cmd(self, a: Any, b: Any) -> str:
        return self.boot(f"{_s(a)} | {_s(b)}")

    def grep(self, pattern: Any, text: Any) -> List[str]:
        pat = _s(pattern)
        return [ln for ln in _s(text).splitlines() if pat in ln]

    # --- workload queue & scheduling (items 32–38 as builtins) ---

    def make_queue(self, name: Any = "default") -> str:
        key = _s(name)
        self.queues.setdefault(key, [])
        return key

    def enqueue(self, q: Any, item: Any) -> int:
        key = _s(q)
        self.queues.setdefault(key, []).append(item)
        return len(self.queues[key])

    def dequeue(self, q: Any) -> Any:
        key = _s(q)
        if not self.queues.get(key):
            raise OsCynError("dequeue on empty workload queue.")
        return self.queues[key].pop(0)

    def schedule(self, secs: Any, fn: Callable) -> None:
        threading.Timer(float(secs), fn).start()

    def logfile(self, path: Any, msg: Any) -> None:
        self.append(path, f"[{datetime.now().isoformat()}] {_s(msg)}\n")

    # --- system info (items 39–53) ---

    def memuse(self) -> Dict[str, Any]:
        try:
            with open("/proc/meminfo", "r", encoding="utf-8") as f:
                lines = f.read().splitlines()
            info = {}
            for ln in lines[:3]:
                k, v = ln.split(":", 1)
                info[k.strip()] = v.strip()
            return info
        except OSError:
            return {"MemTotal": "unknown", "note": "memuse needs /proc"}

    def diskuse(self, path: Any = ".") -> Dict[str, Any]:
        u = shutil.disk_usage(self._path(path))
        return {"total": u.total, "used": u.used, "free": u.free}

    def uptime(self) -> float:
        try:
            with open("/proc/uptime", "r", encoding="utf-8") as f:
                return float(f.read().split()[0])
        except OSError:
            return time.time() - ps_boot_fallback()

    def hostname(self) -> str:
        return socket.gethostname()

    def platform_info(self) -> str:
        return platform.platform()

    def user(self) -> str:
        return os.getenv("USER", os.getenv("USERNAME", "drone"))

    def args(self) -> List[str]:
        return sys.argv[:]

    def now(self) -> float:
        return time.time()

    def date_str(self) -> str:
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def pid(self) -> int:
        return os.getpid()

    def kill_proc(self, proc_pid: Any) -> None:
        os.kill(int(proc_pid), 15)

    def json_read(self, path: Any) -> Any:
        return json.loads(self.read(path))

    def json_write(self, path: Any, data: Any) -> str:
        return self.write(path, json.dumps(data, indent=2))

    def bundle_manifest(self, path: Any) -> Dict[str, Any]:
        return {
            "os": OS_WORDS["CopperOS"],
            "kernel": OS_WORDS["Kernel"],
            "path": self._path(path),
            "exists": self.exist(path),
            "stat": self.stat(path) if self.exist(path) else {},
        }

    def builtins(self) -> Dict[str, Any]:
        return {
            "boot": self.boot,
            "exec_code": self.exec_code,
            "fork_proc": self.fork_proc,
            "getenv": self.getenv,
            "setenv": self.setenv,
            "pwd": self.pwd,
            "cd": self.cd,
            "ls": self.ls,
            "read": self.read,
            "write": self.write,
            "append": self.append,
            "erase": self.erase,
            "exist": self.exist,
            "mkdir": self.mkdir,
            "rmdir": self.rmdir,
            "copy": self.copy,
            "move": self.move,
            "stat": self.stat,
            "chmod": self.chmod,
            "which": self.which,
            "pipe_cmd": self.pipe_cmd,
            "grep": self.grep,
            "make_queue": self.make_queue,
            "enqueue": self.enqueue,
            "dequeue": self.dequeue,
            "schedule": self.schedule,
            "logfile": self.logfile,
            "memuse": self.memuse,
            "diskuse": self.diskuse,
            "uptime": self.uptime,
            "hostname": self.hostname,
            "platform_info": self.platform_info,
            "user": self.user,
            "args": self.args,
            "now": self.now,
            "date_str": self.date_str,
            "pid": self.pid,
            "kill_proc": self.kill_proc,
            "json_read": self.json_read,
            "json_write": self.json_write,
            "bundle_manifest": self.bundle_manifest,
            "deploy_unit": self.deploy,
            "launch_unit": self.launch,
            "halt_unit": self.halt,
            "unit_status": self.status,
            "install_unit": lambda n, t, p: self.install_unit(n, t, p),
            "uninstall_unit": self.uninstall,
            **OS_WORDS,
        }


def ps_boot_fallback() -> float:
    return 0.0
