"""Permissioned backend wrapper — folder access enforcement.

Wraps any backend with an allow-list check on every file operation.
Uses the deepagents SDK's documented delegation pattern.

Inherits from ``SandboxBackendProtocol`` (not just ``BackendProtocol``)
so that ``CompositeBackend.execute()`` isinstance check passes —
without this, shell execution raises ``NotImplementedError``.

``execute`` is forwarded without checks — it remains HITL-gated.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from deepagents.backends.protocol import (
    EditResult,
    ExecuteResponse,
    FileDownloadResponse,
    FileUploadResponse,
    SandboxBackendProtocol,
    WriteResult,
)

if TYPE_CHECKING:
    from deepagents.backends.protocol import FileInfo, GrepMatch

    from icarus.config.permissions import PermissionConfig


class PermissionedBackend(SandboxBackendProtocol):
    """Wrapper that gates file operations against a folder allow-list.

    Inherits ``SandboxBackendProtocol`` so ``CompositeBackend`` sees
    this as an executable backend (isinstance check at execute()).

    All file methods are delegated to *inner* after checking paths
    via ``PermissionConfig.is_path_allowed()``.

    ``glob_info`` and ``grep_raw`` delegate first, then filter
    results — this handles cases where a broad search returns
    matches from outside the allowed set.

    ``execute`` is forwarded unconditionally (HITL-gated elsewhere).
    """

    def __init__(self, inner: SandboxBackendProtocol, config: PermissionConfig) -> None:
        self.inner = inner
        self.config = config

    @property
    def id(self) -> str:
        """Delegate sandbox id to inner backend."""
        return getattr(self.inner, "id", "permissioned")

    def _check(self, path: str) -> str | None:
        """Return error message if *path* is denied, ``None`` if allowed."""
        if self.config.is_path_allowed(path):
            return None
        return f"Access denied: {path} is outside your allowed folders"

    # ---- reads ----

    def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
        if err := self._check(file_path):
            return f"Error: {err}"
        return self.inner.read(file_path, offset=offset, limit=limit)

    def ls_info(self, path: str) -> list[FileInfo]:
        if self._check(path):
            return []
        return self.inner.ls_info(path)

    # ---- search (delegate then filter) ----

    def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
        results = self.inner.glob_info(pattern, path)
        return [r for r in results if self.config.is_path_allowed(r["path"])]

    def grep_raw(
        self,
        pattern: str,
        path: str | None = None,
        glob: str | None = None,
    ) -> list[GrepMatch] | str:
        results = self.inner.grep_raw(pattern, path, glob)
        if isinstance(results, str):
            return results
        return [r for r in results if self.config.is_path_allowed(r["path"])]

    # ---- writes ----

    def write(self, file_path: str, content: str) -> WriteResult:
        if err := self._check(file_path):
            return WriteResult(error=err)
        return self.inner.write(file_path, content)

    def edit(
        self,
        file_path: str,
        old_string: str,
        new_string: str,
        replace_all: bool = False,
    ) -> EditResult:
        if err := self._check(file_path):
            return EditResult(error=err)
        return self.inner.edit(file_path, old_string, new_string, replace_all)

    # ---- upload/download ----

    def upload_files(
        self, files: list[tuple[str, bytes]]
    ) -> list[FileUploadResponse]:
        has_denied = any(self._check(p) for p, _ in files)
        if has_denied:
            return [
                FileUploadResponse(path=p, error="permission_denied")
                for p, _ in files
            ]
        return self.inner.upload_files(files)

    def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
        has_denied = any(self._check(p) for p in paths)
        if has_denied:
            return [
                FileDownloadResponse(path=p, error="permission_denied")
                for p in paths
            ]
        return self.inner.download_files(paths)

    # ---- shell (forwarded, HITL-gated elsewhere) ----

    def execute(
        self, command: str, *, timeout: int | None = None,
    ) -> ExecuteResponse:
        return self.inner.execute(command, timeout=timeout)
