import pytest
from pathlib import Path
from fs_mcp import server
import tempfile
import shutil

@pytest.fixture
def temp_env(tmp_path):
    """Sets up a safe temporary directory environment with all tools enabled for testing"""
    server.initialize([str(tmp_path)], use_all_tools=True)
    return tmp_path

def test_security_barrier(temp_env):
    """Attempting to access outside the temp dir should fail"""
    outside = Path("/etc/passwd")
    
    with pytest.raises(ValueError, match="Access denied"):
        server.validate_path(str(outside))


def test_flag_toggle_dangerous_skip_permissions(temp_env):
    """FS_MCP_FLAG should enable broad access and disable immediately when removed."""
    outside = Path("/etc/passwd")

    with pytest.raises(ValueError, match="Access denied"):
        server.validate_path(str(outside))

    flag = temp_env / server.DANGEROUS_SKIP_PERMISSIONS_FLAG
    flag.write_text("enable dangerous skip", encoding="utf-8")

    resolved = server.validate_path(str(outside))
    assert resolved == outside.resolve()

    flag.unlink()

    with pytest.raises(ValueError, match="Access denied"):
        server.validate_path(str(outside))


@pytest.mark.asyncio
async def test_propose_and_review_receives_dangerous_flag_state(temp_env, monkeypatch):
    """propose_and_review should forward FS_MCP_FLAG state to edit logic."""
    observed_flags = []

    async def fake_propose_and_review_logic(*args, **kwargs):
        observed_flags.append(kwargs.get("dangerous_skip_permissions"))
        return "ok"

    monkeypatch.setattr(server, "propose_and_review_logic", fake_propose_and_review_logic)

    await server.propose_and_review.fn(
        path=str(temp_env / "dummy.txt"),
        new_string="new",
        match_text="old",
        expected_replacements=1,
        session_path=None,
        edits=None,
        bypass_match_text_limit=False,
    )
    assert observed_flags[-1] is False

    flag = temp_env / server.DANGEROUS_SKIP_PERMISSIONS_FLAG
    flag.write_text("enable dangerous skip", encoding="utf-8")

    await server.propose_and_review.fn(
        path=str(temp_env / "dummy.txt"),
        new_string="new",
        match_text="old",
        expected_replacements=1,
        session_path=None,
        edits=None,
        bypass_match_text_limit=False,
    )
    assert observed_flags[-1] is True



def test_write_and_read(temp_env):
    """Test basic read/write tools"""
    target = temp_env / "test.txt"
    
    # Write (Access .fn to call underlying function)
    server.write_file.fn(str(target), "Hello MCP")
    assert target.exists()
    
    # Read
    content = server.read_files.fn([{"path": str(target)}])
    assert "Hello MCP" in content

def test_read_multiple_files(temp_env):
    """Test reading multiple files"""
    f1 = temp_env / "f1.txt"
    f2 = temp_env / "f2.txt"

    server.write_file.fn(str(f1), "Content 1")
    server.write_file.fn(str(f2), "Content 2")

    # Test valid + invalid path mixed
    requests = [
        {"path": str(f1)},
        {"path": str(f2)},
        {"path": str(temp_env / "missing.txt")}
    ]
    result = server.read_files.fn(requests)

    assert "Content 1" in result
    assert "Content 2" in result
    assert "missing.txt" in result
    assert "Error" in result # For the missing file
    assert "---" in result



def test_read_files_multiple_modes_per_file(temp_env):
    """A single file request can contain multiple read mode specs via `reads`."""
    target = temp_env / "multi_modes.txt"
    content = "\n".join(f"line {i}" for i in range(1, 201)) + "\n"
    server.write_file.fn(str(target), content)

    result = server.read_files.fn([
        {
            "path": str(target),
            "reads": [
                {"head": 3},
                {"start_line": 100, "end_line": 102},
            ],
        }
    ])

    assert f"File: {target} [read 1/2: head=3]" in result
    assert f"File: {target} [read 2/2: range=100:102]" in result
    assert "line 1" in result
    assert "line 3" in result
    assert "line 100" in result
    assert "line 102" in result



def test_read_files_rejects_mixed_reads_and_legacy_fields(temp_env):
    """`reads` cannot be combined with top-level legacy read fields."""
    target = temp_env / "mixed_modes.txt"
    server.write_file.fn(str(target), "hello\nworld\n")

    result = server.read_files.fn([
        {
            "path": str(target),
            "head": 1,
            "reads": [{"tail": 1}],
        }
    ])

    assert "Error: Invalid request shape." in result
    assert "reads` cannot be combined with top-level" in result

def test_list_directory(temp_env):
    """Test directory listing"""
    (temp_env / "A").mkdir()
    (temp_env / "B.txt").touch()
    
    res = server.list_directory.fn(str(temp_env))
    assert "[DIR] A" in res
    assert "[FILE] B.txt" in res

def test_relative_path_resolution(temp_env):
    """Test that relative paths are resolved correctly."""
    # Create a subdirectory and a file within it
    sub_dir = temp_env / "sub"
    sub_dir.mkdir()
    target_file = sub_dir / "relative_test.txt"
    target_file.touch()

    # Attempt to validate the path using a relative path
    # The server should resolve this relative to the temp_env
    resolved_path = server.validate_path("sub/relative_test.txt")

    # Assert that the resolved path is correct and absolute
    assert resolved_path == target_file.resolve()

def test_temp_file_access_security(temp_env):
    """Test security restrictions for temporary file access."""
    # This test simulates the `propose_and_review` workflow.
    
    # 1. Create a mock review directory in the actual temp location
    real_temp_dir = Path(tempfile.gettempdir())
    review_dir = real_temp_dir / "mcp_review_abc123"
    review_dir.mkdir(exist_ok=True)
    
    # 2. Create valid and invalid files within the mock review dir
    valid_file = review_dir / "current_test.py"
    invalid_file = review_dir / "some_other_file.txt"
    valid_file.touch()
    invalid_file.touch()
    
    # 3. Create a file in a non-review temp directory
    non_review_dir = real_temp_dir / "not_a_review_dir"
    non_review_dir.mkdir(exist_ok=True)
    rogue_file = non_review_dir / "rogue.txt"
    rogue_file.touch()

    # --- Assertions ---
    
    # a) The agent SHOULD be able to access the 'current_' file.
    try:
        # We expect this to succeed. If it raises an error, the test fails.
        resolved_path = server.validate_path(str(valid_file))
        assert resolved_path.exists()
    except ValueError:
        pytest.fail("Validation of a valid temp file unexpectedly failed.")

    # b) The agent SHOULD NOT be able to access a file that doesn't match the expected pattern.
    with pytest.raises(ValueError, match="Access denied"):
        server.validate_path(str(invalid_file))

    # c) The agent SHOULD NOT be able to access files in other temp directories.
    with pytest.raises(ValueError, match="Access denied"):
        server.validate_path(str(rogue_file))
        
def test_literal_newline_roundtrip(temp_env):
    """Test that files with literal \\n are preserved through read/write/edit roundtrips."""
    target = temp_env / "test_newline.py"

    # --- Step 1: Create a file with literal \n escape sequences ---
    raw_content = 'print("Hello\\nWorld")'
    server.write_file.fn(str(target), raw_content)

    # --- Step 2: read_files should return the content as-is ---
    read_result = server.read_files.fn([{"path": str(target)}])
    assert 'print("Hello\\nWorld")' in read_result

    # --- Step 3: Edit using exact content from read ---
    # Using 'match_text' instead of 'old_string'
    tool = server.RooStyleEditTool(validate_path_func=lambda p: Path(p))
    # Note: _prepare_edit signature is (path, match_text, new_string, ...)
    prep_result = tool._prepare_edit(
        file_path=str(target),
        match_text='print("Hello\\nWorld")',
        new_string='print("Hi\\nUniverse")',
        expected_replacements=1
    )

    assert prep_result.success
    assert prep_result.new_content == 'print("Hi\\nUniverse")'

    # --- Step 4: Write back and verify file on disk ---
    server.write_file.fn(str(target), prep_result.new_content)
    final_content = target.read_text(encoding='utf-8')
    assert final_content == 'print("Hi\\nUniverse")'

    # --- Step 5: Re-read and confirm no corruption ---
    re_read = server.read_files.fn([{"path": str(target)}])
    assert 'print("Hi\\nUniverse")' in re_read