"""Write DyzineFrame config.ini without killing threads, and never leave it root:root.

Heltec /asset persist used to `open(config.ini,'w')` uncaught. If the file was
root:root (sudo python, scp as root), PermissionError killed the USB CDC thread
and the Heltec latched USB FAILED even while 303a:0002 stayed on the bus.
"""
from __future__ import annotations

import os
import stat
from typing import Callable, Optional, Tuple

CONFIG_OWNER_USER = "topbox"
CONFIG_FILE_MODE = 0o664


def call_never_raise(fn: Callable[[], None], *, label: str = "call") -> bool:
    """Run fn. Return False on any exception. Never raise — USB CDC thread depends on this."""
    try:
        fn()
        return True
    except Exception as exc:
        print(f"{label} failed (thread kept alive): {exc}", flush=True)
        return False


def warn_if_config_unwritable(path: str) -> bool:
    """True if path (or its parent, if missing) looks writable. Never raise."""
    try:
        if os.path.exists(path):
            if not os.access(path, os.W_OK):
                print(
                    f"WARN: {path} is not writable (often root:root). "
                    "Heltec /asset will not persist. chown topbox:topbox that file.",
                    flush=True,
                )
                return False
            return True
        parent = os.path.dirname(os.path.abspath(path)) or "."
        if not os.access(parent, os.W_OK):
            print(f"WARN: cannot create {path} (parent not writable)", flush=True)
            return False
        return True
    except OSError as exc:
        print(f"WARN: config.ini writable check failed: {exc}", flush=True)
        return False


def write_config_ini(config, path: str) -> bool:
    """Persist config.ini. Must not raise. If euid is 0, chown to the tree owner."""
    try:
        with open(path, "w", encoding="utf-8") as handle:
            config.write(handle)
        _chmod_config_ini(path)
        _chown_away_from_root(path)
        return True
    except OSError as exc:
        print(f"WARN: could not write {path}: {exc}", flush=True)
        return False
    except Exception as exc:
        print(f"WARN: could not write {path}: {exc}", flush=True)
        return False


def _euid() -> int:
    geteuid = getattr(os, "geteuid", None)
    if geteuid is None:
        return 1
    try:
        return int(geteuid())
    except OSError:
        return 1


def _chmod_config_ini(path: str) -> None:
    try:
        os.chmod(path, CONFIG_FILE_MODE)
    except OSError:
        pass


def target_uid_gid(path: str) -> Optional[Tuple[int, int]]:
    """Owner that should hold config.ini: parent dir if not root, else user topbox."""
    parent = os.path.dirname(os.path.abspath(path)) or "."
    try:
        st = os.stat(parent)
        if int(st.st_uid) != 0:
            return int(st.st_uid), int(st.st_gid)
    except OSError:
        pass
    try:
        import pwd  # Unix only

        pw = pwd.getpwnam(CONFIG_OWNER_USER)
        return int(pw.pw_uid), int(pw.pw_gid)
    except (ImportError, KeyError, OSError, AttributeError):
        return None


def _chown_away_from_root(path: str) -> None:
    """If we just wrote as root, chown to the DyzineFrame tree owner. No-op on Windows."""
    chown = getattr(os, "chown", None)
    if chown is None or _euid() != 0:
        return
    try:
        st = os.stat(path)
    except OSError:
        return
    if int(st.st_uid) != 0 and int(st.st_gid) != 0:
        return
    owner = target_uid_gid(path)
    if owner is None:
        print(
            f"WARN: {path} written as root; could not resolve {CONFIG_OWNER_USER} for chown",
            flush=True,
        )
        return
    try:
        chown(path, owner[0], owner[1])
    except OSError as exc:
        print(f"WARN: {path} is root-owned; chown failed: {exc}", flush=True)


def is_root_owned(path: str) -> bool:
    """True when the file exists and uid is 0. False on Windows or if missing."""
    if os.name == "nt" or not hasattr(os, "geteuid"):
        return False
    try:
        st = os.stat(path)
    except OSError:
        return False
    return int(st.st_uid) == 0 and not stat.S_ISDIR(st.st_mode)
