from parameter_decorators import path_to_str

from rpcclient.clients.darwin.structs import stat64, statfs64
from rpcclient.core.subsystems.fs import Fs, RemotePath


def do_stat(client, stat_name, filename: str):
    """Return a stat64 struct for a remote path."""
    with client.safe_malloc(stat64.sizeof()) as buf:
        err = client.symbols[stat_name](filename, buf)
        if err != 0:
            client.raise_errno_exception(f"failed to stat(): {filename}")
        return stat64.parse_stream(buf)


class DarwinRemotePath(RemotePath):
    def __init__(self, path: str, client) -> None:
        super().__init__(path, client)

    def stat(self):
        return do_stat(self._client, "stat64", self._path)

    def lstat(self):
        return do_stat(self._client, "lstat64", self._path)


class DarwinFs(Fs):
    @path_to_str("path")
    def stat(self, path: str):
        """Return stat64 info for a remote path."""
        return do_stat(self._client, "stat64", path)

    @path_to_str("path")
    def lstat(self, path: str):
        """Return lstat64 info for a remote path (does not follow symlinks)."""
        return do_stat(self._client, "lstat64", path)

    @path_to_str("path")
    def setxattr(self, path: str, name: str, value: bytes):
        """set an extended attribute value"""
        count = self._client.symbols.setxattr(path, name, value, len(value), 0, 0).c_int64
        if count == -1:
            self._client.raise_errno_exception(f"failed to setxattr(): {path}")

    @path_to_str("path")
    def removexattr(self, path: str, name: str):
        """remove an extended attribute value"""
        count = self._client.symbols.removexattr(path, name, 0).c_int64
        if count == -1:
            self._client.raise_errno_exception(f"failed to removexattr(): {path}")

    @path_to_str("path")
    def listxattr(self, path: str) -> list[str]:
        """list extended attribute names"""
        max_buf_len = 1024
        with self._client.safe_malloc(max_buf_len) as xattributes_names:
            count = self._client.symbols.listxattr(path, xattributes_names, max_buf_len, 0).c_int64
            if count == -1:
                self._client.raise_errno_exception(f"failed to listxattr(): {path}")
            return [s.decode() for s in xattributes_names.peek(count).split(b"\x00")[:-1]]

    @path_to_str("path")
    def getxattr(self, path: str, name: str) -> bytes:
        """get an extended attribute value"""
        max_buf_len = 1024
        with self._client.safe_malloc(max_buf_len) as value:
            count = self._client.symbols.getxattr(path, name, value, max_buf_len, 0, 0).c_int64
            if count == -1:
                self._client.raise_errno_exception(f"failed to getxattr(): {path}")
            return value.peek(count)

    @path_to_str("path")
    def dictxattr(self, path: str) -> dict[str, bytes]:
        """get a dictionary of all extended attributes"""
        result = {}
        for k in self.listxattr(path):
            result[k] = self.getxattr(path, k)
        return result

    @path_to_str("path")
    def statfs(self, path: str):
        with self._client.safe_malloc(statfs64.sizeof()) as buf:
            if self._client.symbols.statfs64(path, buf) != 0:
                self._client.raise_errno_exception(f"statfs failed for: {path}")
            return statfs64.parse_stream(buf)

    @path_to_str("path")
    def chflags(self, path: str, flags: int) -> None:
        """Set BSD file flags on a remote path."""
        if self._client.symbols.chflags(path, flags) != 0:
            self._client.raise_errno_exception(f"chflags failed for: {path}")

    def remote_path(self, path: str) -> DarwinRemotePath:
        return DarwinRemotePath(path, self._client)
