"""Tests for the /api/files sandbox — _resolve_sandboxed must block path traversal."""
from __future__ import annotations

import sys
from pathlib import Path

import pytest

PROJECT_ROOT = Path(__file__).resolve().parents[1]
DASH = PROJECT_ROOT / "dashboard"

if str(DASH) not in sys.path:
    sys.path.insert(0, str(DASH))


@pytest.fixture(scope="module")
def files_module():
    from handlers import files as _f  # type: ignore
    return _f


@pytest.fixture
def handler(files_module):
    class _Stub(files_module.FilesHandlers):
        def __init__(self):
            self.captured = None
            self.captured_code = None

        def send_json(self, data, code=200):
            self.captured = data
            self.captured_code = code

    return _Stub()


@pytest.fixture
def sandboxed(tmp_path, monkeypatch):
    """Replace ALLOWED_WORKSPACES with a single tmp_path to isolate tests."""
    import constants  # type: ignore
    root = tmp_path / "repo"
    root.mkdir()
    monkeypatch.setattr(constants, "ALLOWED_WORKSPACES", [root])
    return root


def test_resolve_accepts_file_inside_workspace(handler, sandboxed):
    (sandboxed / "notes.md").write_text("hello", encoding="utf-8")
    target, workspace = handler._resolve_sandboxed("notes.md")
    assert target is not None
    assert target == (sandboxed / "notes.md").resolve()
    assert workspace == sandboxed


def test_resolve_accepts_nested_file(handler, sandboxed):
    (sandboxed / "sub").mkdir()
    (sandboxed / "sub" / "file.txt").write_text("x", encoding="utf-8")
    target, workspace = handler._resolve_sandboxed("sub/file.txt")
    assert target == (sandboxed / "sub" / "file.txt").resolve()
    assert workspace == sandboxed


def test_resolve_rejects_parent_traversal(handler, sandboxed):
    """../etc/passwd-style requests must resolve OUTSIDE the workspace and
    be refused by returning (None, None)."""
    target, workspace = handler._resolve_sandboxed("../../../etc/passwd")
    assert target is None
    assert workspace is None


def test_resolve_rejects_absolute_escape(handler, sandboxed):
    """Absolute path that lands outside the workspace must be refused.

    Note: Path('/base') / '/etc/passwd' == Path('/etc/passwd') because the
    second path is absolute. The resolver must still refuse it.
    """
    target, workspace = handler._resolve_sandboxed("/etc/passwd")
    assert target is None, "absolute outside path must be refused"
    assert workspace is None


def test_handle_files_get_returns_403_on_denied(handler, sandboxed):
    """End-to-end: /api/files with a traversal path must 403."""
    # Simulate HTTP path parsing: the endpoint reads self.path
    handler.path = "/api/files?path=../../../etc/passwd&action=list"
    handler.handle_files_get()
    assert handler.captured_code == 403
    assert handler.captured["error"] == "Access denied"


def test_handle_files_get_reads_a_file(handler, sandboxed):
    (sandboxed / "hello.txt").write_text("hi there", encoding="utf-8")
    handler.path = "/api/files?path=hello.txt&action=list"
    handler.handle_files_get()
    assert handler.captured_code == 200 or handler.captured_code is None
    assert handler.captured["type"] == "file"
    assert handler.captured["content"] == "hi there"


def test_handle_files_get_lists_a_dir(handler, sandboxed):
    (sandboxed / "a.md").write_text("a", encoding="utf-8")
    (sandboxed / "b.md").write_text("b", encoding="utf-8")
    (sandboxed / "nested").mkdir()
    handler.path = "/api/files?path=&action=list"
    handler.handle_files_get()
    assert handler.captured["type"] == "dir"
    names = {item["name"] for item in handler.captured["items"]}
    assert names == {"a.md", "b.md", "nested"}
