#!/usr/bin/env python3
"""Root/capability checks, privilege dropping, directory permission enforcement."""

import ctypes
import ctypes.util
import grp
import os
import pwd
import stat
from pathlib import Path
from typing import Optional

# Linux capability constants
CAP_NET_RAW = 13
CAP_NET_ADMIN = 15
CAP_SYS_ADMIN = 21
CAP_DAC_OVERRIDE = 1

# capget/capset structures
_LINUX_CAPABILITY_VERSION_3 = 0x20080522
_VFS_CAP_REVISION_2 = 0x02000000

_libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)


class _cap_header(ctypes.Structure):
    _fields_ = [
        ("version", ctypes.c_uint32),
        ("pid", ctypes.c_int),
    ]


class _cap_data(ctypes.Structure):
    _fields_ = [
        ("effective", ctypes.c_uint32),
        ("permitted", ctypes.c_uint32),
        ("inheritable", ctypes.c_uint32),
    ]


def check_root() -> bool:
    return os.geteuid() == 0


def check_capability(cap: int) -> bool:
    if check_root():
        return True

    try:
        header = _cap_header(version=_LINUX_CAPABILITY_VERSION_3, pid=0)
        # Version 3 uses 2 data structs for 64-bit capability sets
        data = (_cap_data * 2)()
        ret = _libc.capget(ctypes.byref(header), data)
        if ret != 0:
            return False

        idx = cap // 32
        bit = 1 << (cap % 32)
        return bool(data[idx].effective & bit)
    except Exception:
        return False


def has_net_raw() -> bool:
    return check_capability(CAP_NET_RAW)


def has_net_admin() -> bool:
    return check_capability(CAP_NET_ADMIN)


def check_required_capabilities() -> dict:
    return {
        "root": check_root(),
        "CAP_NET_RAW": check_capability(CAP_NET_RAW),
        "CAP_NET_ADMIN": check_capability(CAP_NET_ADMIN),
        "CAP_SYS_ADMIN": check_capability(CAP_SYS_ADMIN),
    }


def drop_privileges(
    username: str = "nobody",
    groupname: Optional[str] = None,
    keep_caps: Optional[list] = None,
) -> bool:
    if not check_root():
        return False

    try:
        pw = pwd.getpwnam(username)
        uid = pw.pw_uid
        gid = pw.pw_gid

        if groupname:
            gr = grp.getgrnam(groupname)
            gid = gr.gr_gid

        # Set supplementary groups
        os.setgroups([])

        # Set GID first (can't change after dropping root)
        os.setregid(gid, gid)
        os.setreuid(uid, uid)

        # Verify drop
        if os.geteuid() == 0:
            return False

        # Re-apply specific capabilities if requested
        if keep_caps:
            _set_keepcaps()
            _apply_caps(keep_caps)

        return True
    except (KeyError, PermissionError, OSError):
        return False


def _set_keepcaps():
    PR_SET_KEEPCAPS = 8
    _libc.prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)


def _apply_caps(caps: list):
    header = _cap_header(version=_LINUX_CAPABILITY_VERSION_3, pid=0)
    data = (_cap_data * 2)()

    for cap in caps:
        idx = cap // 32
        bit = 1 << (cap % 32)
        data[idx].effective |= bit
        data[idx].permitted |= bit

    _libc.capset(ctypes.byref(header), data)


def enforce_directory_permissions(
    path: str,
    mode: int = 0o700,
    recursive: bool = True,
) -> int:
    count = 0
    target = Path(path)

    if not target.exists():
        target.mkdir(parents=True, mode=mode)
        count += 1
        return count

    current = target.stat().st_mode & 0o777
    if current != mode:
        target.chmod(mode)
        count += 1

    if recursive:
        for item in target.rglob("*"):
            if item.is_dir():
                item_mode = item.stat().st_mode & 0o777
                if item_mode != mode:
                    item.chmod(mode)
                    count += 1
            elif item.is_file():
                file_mode = mode & 0o700  # Files get owner-only based on dir mode
                item_mode = item.stat().st_mode & 0o777
                if item_mode != file_mode:
                    item.chmod(file_mode)
                    count += 1

    return count


def secure_path(path: str, owner_uid: Optional[int] = None) -> bool:
    try:
        p = Path(path)
        if not p.exists():
            return False

        # Set ownership if specified
        if owner_uid is not None:
            os.chown(path, owner_uid, owner_uid)

        # Restrict permissions
        if p.is_dir():
            p.chmod(0o700)
        else:
            p.chmod(0o600)

        return True
    except (OSError, PermissionError):
        return False
