"""Shared seat-scope constants and helpers (no cross-module cycles)."""

from __future__ import annotations

import errno
import os
import re
import stat
import sys

SEAT_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
CONTROL_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
LINUX_USERNAME_RE = re.compile(r"^[a-z][a-z0-9_-]{0,30}$")
MAX_LINUX_USERNAME = 32
IMPLEMENTER_USER_PREFIX = "ods-"
DEFAULT_IMPLEMENTER_RUNTIME_BASE = "/var/lib/overdeck/seat-runtime"
DEFAULT_SEAT_CONTROL_BASE = "/var/lib/overdeck/seat-control"
CONTROL_BASE_MODE = 0o711
CONTROL_DIR_MODE = 0o711
CONTROL_OWNER_MARKER_MODE = 0o600
CONTROL_NETNS_INODE_MODE = 0o644
TMUX_CONF_MODE = 0o644
OWNER_MARKER_PREFIX = "overdeck-seat-owner="
NOLOGIN_SHELL = "/usr/sbin/nologin"
SEAT_TRUSTED_CONFIG_DIR = "trusted-config"
SEAT_GIT_EMAIL_DOMAIN = "overdeck.invalid"
LAUNCH_AUTHORITY_FILES = (
    "seat-settings.json",
    "empty-mcp.json",
    "claude-forwarded.json",
    "seat-env.json",
)
SEAT_LAUNCHER_BIN = "/usr/local/bin/overdeck-seat-launcher"
SEAT_STARTUP_STATUS_FILE = "startup.status"
SEAT_STARTUP_STATUS_MAX_BYTES = 64
SEAT_STARTUP_STATUS_MARKERS = frozenset(
    {
        "starting",
        "env-json",
        "seat-metadata",
        "profile-nonconcrete",
        "profile-dir",
        "secure-storage",
        "account-slug",
        "netns-path",
        "netns-membership",
        "claude-bin",
        "guard-bin",
        "checkout",
        "artifacts",
        "forwarded",
        "model-count",
        "exec",
    }
)
SEAT_STARTUP_STATUS_MARKER_RE = re.compile(
    r"^(?:starting|env-json|seat-metadata|profile-nonconcrete|profile-dir|secure-storage|"
    r"account-slug|netns-path|netns-membership|claude-bin|"
    r"guard-bin|checkout|artifacts|forwarded|model-count|exec|exit-\d+)$"
)


def startup_status_path(state_dir: str) -> str:
    return os.path.join(os.path.normpath(state_dir), SEAT_STARTUP_STATUS_FILE)


def validate_startup_status_content(raw: bytes) -> str | None:
    if not raw or len(raw) > SEAT_STARTUP_STATUS_MAX_BYTES:
        return None
    if raw.endswith(b"\n"):
        if len(raw) == 1:
            return None
        body = raw[:-1]
        if b"\n" in body:
            return None
    else:
        body = raw
    if b"\r" in body or b"\x00" in body:
        return None
    try:
        text = body.decode("utf-8", "strict")
    except UnicodeDecodeError:
        return None
    if any(ch.isspace() for ch in text):
        return None
    if CONTROL_RE.search(text):
        return None
    if not SEAT_STARTUP_STATUS_MARKER_RE.fullmatch(text):
        return None
    return text


def _startup_status_open_flags() -> int | None:
    flags = os.O_RDONLY
    if hasattr(os, "O_CLOEXEC"):
        flags |= os.O_CLOEXEC
    if hasattr(os, "O_NONBLOCK"):
        flags |= os.O_NONBLOCK
    if not hasattr(os, "O_NOFOLLOW"):
        return None
    flags |= os.O_NOFOLLOW
    return flags


def read_startup_status_marker(state_dir: str) -> str:
    path = startup_status_path(state_dir)
    flags = _startup_status_open_flags()
    if flags is None:
        return "unavailable"
    fd = -1
    try:
        fd = os.open(path, flags)
        try:
            st = os.fstat(fd)
        except OSError:
            return "unavailable"
        if stat.S_ISLNK(st.st_mode):
            return "invalid"
        if not stat.S_ISREG(st.st_mode):
            return "invalid"
        if st.st_size > SEAT_STARTUP_STATUS_MAX_BYTES:
            return "invalid"
        try:
            raw = os.read(fd, SEAT_STARTUP_STATUS_MAX_BYTES + 1)
        except OSError:
            return "unavailable"
        if len(raw) > SEAT_STARTUP_STATUS_MAX_BYTES:
            return "invalid"
        validated = validate_startup_status_content(raw)
        return validated if validated else "invalid"
    except FileNotFoundError:
        return "unavailable"
    except OSError as exc:
        if exc.errno in (errno.ELOOP, errno.EISDIR):
            return "invalid"
        return "unavailable"
    finally:
        if fd >= 0:
            try:
                os.close(fd)
            except OSError:
                pass


def remove_startup_status(state_dir: str) -> None:
    path = startup_status_path(state_dir)
    try:
        os.unlink(path)
    except FileNotFoundError:
        pass
    except OSError:
        pass


def implementer_runtime_base() -> str:
    return os.environ.get("OVERDECK_SEAT_RUNTIME_BASE", DEFAULT_IMPLEMENTER_RUNTIME_BASE)


def seat_control_base() -> str:
    return os.environ.get("OVERDECK_SEAT_CONTROL_BASE", DEFAULT_SEAT_CONTROL_BASE)


def seat_test_mode() -> bool:
    return os.environ.get("OVERDECK_SEAT_TEST_MODE") == "1"


def die(msg: str, code: int = 2) -> None:
    print(msg, file=sys.stderr)
    raise SystemExit(code)
