"""Tests for PermissionedBackend wrapper."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock

import pytest

from deepagents.backends.protocol import (
    EditResult,
    ExecuteResponse,
    FileDownloadResponse,
    FileUploadResponse,
    WriteResult,
)
from icarus.config.permissions import PermissionConfig
from icarus.runtime.backend import PermissionedBackend


@pytest.fixture
def allowed_dir(tmp_path):
    d = tmp_path / "allowed"
    d.mkdir()
    return d


@pytest.fixture
def denied_dir(tmp_path):
    d = tmp_path / "denied"
    d.mkdir()
    return d


@pytest.fixture
def config(allowed_dir, tmp_path, monkeypatch):
    monkeypatch.setattr("icarus.config.permissions.ICARUS_DIR", tmp_path / ".icarus")
    return PermissionConfig(allow_all=False, allowed_folders=[allowed_dir])


@pytest.fixture
def mock_inner():
    inner = MagicMock()
    inner.read.return_value = "file content"
    inner.ls_info.return_value = [{"path": "/some/path", "is_dir": False}]
    inner.write.return_value = WriteResult(path="/ok", files_update=None)
    inner.edit.return_value = EditResult(path="/ok", files_update=None, occurrences=1)
    inner.execute.return_value = ExecuteResponse(output="hello")
    inner.glob_info.return_value = []
    inner.grep_raw.return_value = []
    inner.upload_files.return_value = []
    inner.download_files.return_value = []
    return inner


@pytest.fixture
def backend(mock_inner, config):
    return PermissionedBackend(inner=mock_inner, config=config)


class TestRead:
    def test_allowed_path_delegates(self, backend, mock_inner, allowed_dir):
        result = backend.read(str(allowed_dir / "file.txt"))
        mock_inner.read.assert_called_once()
        assert result == "file content"

    def test_denied_path_returns_error(self, backend, mock_inner, denied_dir):
        result = backend.read(str(denied_dir / "file.txt"))
        mock_inner.read.assert_not_called()
        assert "Access denied" in result


class TestLsInfo:
    def test_allowed_path_delegates(self, backend, mock_inner, allowed_dir):
        backend.ls_info(str(allowed_dir))
        mock_inner.ls_info.assert_called_once()

    def test_denied_path_returns_empty(self, backend, mock_inner, denied_dir):
        result = backend.ls_info(str(denied_dir))
        mock_inner.ls_info.assert_not_called()
        assert result == []


class TestWrite:
    def test_allowed_path_delegates(self, backend, mock_inner, allowed_dir):
        result = backend.write(str(allowed_dir / "new.txt"), "content")
        mock_inner.write.assert_called_once()
        assert result.error is None

    def test_denied_path_returns_error(self, backend, mock_inner, denied_dir):
        result = backend.write(str(denied_dir / "new.txt"), "content")
        mock_inner.write.assert_not_called()
        assert result.error is not None
        assert "Access denied" in result.error


class TestEdit:
    def test_allowed_path_delegates(self, backend, mock_inner, allowed_dir):
        result = backend.edit(str(allowed_dir / "file.txt"), "old", "new")
        mock_inner.edit.assert_called_once()
        assert result.error is None

    def test_denied_path_returns_error(self, backend, mock_inner, denied_dir):
        result = backend.edit(str(denied_dir / "file.txt"), "old", "new")
        mock_inner.edit.assert_not_called()
        assert result.error is not None


class TestGlobInfo:
    def test_filters_results_to_allowed_only(self, backend, mock_inner, allowed_dir, denied_dir):
        mock_inner.glob_info.return_value = [
            {"path": str(allowed_dir / "ok.py")},
            {"path": str(denied_dir / "secret.py")},
        ]
        results = backend.glob_info("**/*.py")
        assert len(results) == 1
        assert str(allowed_dir) in results[0]["path"]

    def test_returns_empty_when_all_filtered(self, backend, mock_inner, denied_dir):
        mock_inner.glob_info.return_value = [
            {"path": str(denied_dir / "secret.py")},
        ]
        results = backend.glob_info("**/*.py")
        assert results == []


class TestGrepRaw:
    def test_filters_results_to_allowed_only(self, backend, mock_inner, allowed_dir, denied_dir):
        mock_inner.grep_raw.return_value = [
            {"path": str(allowed_dir / "ok.py"), "line": 1, "text": "match"},
            {"path": str(denied_dir / "secret.py"), "line": 5, "text": "match"},
        ]
        results = backend.grep_raw("pattern")
        assert len(results) == 1
        assert str(allowed_dir) in results[0]["path"]

    def test_passes_through_error_strings(self, backend, mock_inner):
        mock_inner.grep_raw.return_value = "Invalid regex pattern: [bad"
        result = backend.grep_raw("[bad")
        assert result == "Invalid regex pattern: [bad"


class TestExecute:
    def test_forwards_to_inner_without_permission_check(self, backend, mock_inner):
        mock_inner.execute.return_value = ExecuteResponse(output="hello")
        backend.execute("ls /etc")
        mock_inner.execute.assert_called_once_with("ls /etc", timeout=None)


class TestUploadFiles:
    def test_allowed_paths_delegate(self, backend, mock_inner, allowed_dir):
        files = [(str(allowed_dir / "f.txt"), b"data")]
        mock_inner.upload_files.return_value = [FileUploadResponse(path=files[0][0])]
        result = backend.upload_files(files)
        mock_inner.upload_files.assert_called_once()
        assert result[0].error is None

    def test_denied_path_returns_error(self, backend, mock_inner, denied_dir):
        files = [(str(denied_dir / "f.txt"), b"data")]
        result = backend.upload_files(files)
        mock_inner.upload_files.assert_not_called()
        assert result[0].error == "permission_denied"

    def test_mixed_batch_rejects_all_with_correct_count(self, backend, mock_inner, allowed_dir, denied_dir):
        files = [
            (str(allowed_dir / "ok.txt"), b"data"),
            (str(denied_dir / "bad.txt"), b"data"),
        ]
        result = backend.upload_files(files)
        mock_inner.upload_files.assert_not_called()
        assert len(result) == 2
        assert all(r.error == "permission_denied" for r in result)


class TestDownloadFiles:
    def test_allowed_paths_delegate(self, backend, mock_inner, allowed_dir):
        paths = [str(allowed_dir / "f.txt")]
        mock_inner.download_files.return_value = [
            FileDownloadResponse(path=paths[0], content=b"data")
        ]
        result = backend.download_files(paths)
        mock_inner.download_files.assert_called_once()
        assert result[0].error is None

    def test_denied_path_returns_error(self, backend, mock_inner, denied_dir):
        paths = [str(denied_dir / "f.txt")]
        result = backend.download_files(paths)
        mock_inner.download_files.assert_not_called()
        assert result[0].error == "permission_denied"

    def test_mixed_batch_rejects_all_with_correct_count(self, backend, mock_inner, allowed_dir, denied_dir):
        paths = [str(allowed_dir / "ok.txt"), str(denied_dir / "bad.txt")]
        result = backend.download_files(paths)
        mock_inner.download_files.assert_not_called()
        assert len(result) == 2
        assert all(r.error == "permission_denied" for r in result)
