"""Tests for file manager."""

import pytest
import tempfile
import os
from pathlib import Path
from skop_runner.file_manager import FileManager


@pytest.fixture
def temp_project():
    """Create a temporary project directory."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create some test files and folders
        project = Path(tmpdir)

        # Create directories
        (project / "src").mkdir()
        (project / "src" / "utils").mkdir()
        (project / "tests").mkdir()

        # Create files
        (project / "README.md").write_text("# Test Project")
        (project / "src" / "main.py").write_text("print('hello')")
        (project / "src" / "utils" / "helpers.py").write_text("def helper(): pass")
        (project / "tests" / "test_main.py").write_text("def test_main(): pass")

        yield project


def test_init_valid_directory(temp_project):
    """Test initializing with valid directory."""
    fm = FileManager(str(temp_project))
    # Use resolve() on both sides to handle macOS /var -> /private/var symlink
    assert fm.project_root == temp_project.resolve()


def test_init_invalid_directory():
    """Test initializing with non-existent directory."""
    with pytest.raises(ValueError, match="does not exist"):
        FileManager("/nonexistent/path")


@pytest.mark.asyncio
async def test_list_directory_root(temp_project):
    """Test listing root directory."""
    fm = FileManager(str(temp_project))
    items = await fm.list_directory("")

    names = [item["name"] for item in items]
    assert "src" in names
    assert "tests" in names
    assert "README.md" in names

    # Directories should come first
    dir_items = [i for i in items if i["type"] == "directory"]
    file_items = [i for i in items if i["type"] == "file"]
    assert items.index(dir_items[0]) < items.index(file_items[0])


@pytest.mark.asyncio
async def test_list_directory_subdirectory(temp_project):
    """Test listing subdirectory."""
    fm = FileManager(str(temp_project))
    items = await fm.list_directory("src")

    names = [item["name"] for item in items]
    assert "main.py" in names
    assert "utils" in names


@pytest.mark.asyncio
async def test_list_directory_not_found(temp_project):
    """Test listing non-existent directory."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.list_directory("nonexistent")


def test_path_traversal_attack(temp_project):
    """Test that path traversal is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="traversal"):
        fm.resolve_path("../../../etc/passwd")

    with pytest.raises(ValueError, match="Absolute paths not allowed"):
        fm.resolve_path("/etc/passwd")


@pytest.mark.asyncio
async def test_read_file(temp_project):
    """Test reading a file."""
    fm = FileManager(str(temp_project))
    content = await fm.read_file("README.md")
    assert content == "# Test Project"


@pytest.mark.asyncio
async def test_read_file_not_found(temp_project):
    """Test reading non-existent file."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.read_file("nonexistent.txt")


@pytest.mark.asyncio
async def test_write_file(temp_project):
    """Test writing a file."""
    fm = FileManager(str(temp_project))

    await fm.write_file("new_file.txt", "Hello, World!")

    content = await fm.read_file("new_file.txt")
    assert content == "Hello, World!"


@pytest.mark.asyncio
async def test_write_file_creates_directories(temp_project):
    """Test that write_file creates parent directories."""
    fm = FileManager(str(temp_project))

    await fm.write_file("new/nested/file.txt", "nested content")

    assert (temp_project / "new" / "nested" / "file.txt").exists()
    content = await fm.read_file("new/nested/file.txt")
    assert content == "nested content"


@pytest.mark.asyncio
async def test_create_file(temp_project):
    """Test creating an empty file."""
    fm = FileManager(str(temp_project))

    await fm.create_file("empty.txt")

    assert (temp_project / "empty.txt").exists()
    content = await fm.read_file("empty.txt")
    assert content == ""


@pytest.mark.asyncio
async def test_create_file_already_exists(temp_project):
    """Test creating file that already exists."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileExistsError):
        await fm.create_file("README.md")


@pytest.mark.asyncio
async def test_create_folder(temp_project):
    """Test creating a folder."""
    fm = FileManager(str(temp_project))

    await fm.create_folder("new_folder")

    assert (temp_project / "new_folder").is_dir()


@pytest.mark.asyncio
async def test_create_folder_nested(temp_project):
    """Test creating nested folders."""
    fm = FileManager(str(temp_project))

    await fm.create_folder("a/b/c")

    assert (temp_project / "a" / "b" / "c").is_dir()


@pytest.mark.asyncio
async def test_delete_file(temp_project):
    """Test deleting a file."""
    fm = FileManager(str(temp_project))

    assert (temp_project / "README.md").exists()

    await fm.delete("README.md")

    assert not (temp_project / "README.md").exists()


@pytest.mark.asyncio
async def test_delete_folder(temp_project):
    """Test deleting a folder."""
    fm = FileManager(str(temp_project))

    await fm.delete("src")

    assert not (temp_project / "src").exists()


@pytest.mark.asyncio
async def test_delete_not_found(temp_project):
    """Test deleting non-existent path."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.delete("nonexistent")


@pytest.mark.asyncio
async def test_delete_project_root_prevented(temp_project):
    """Test that deleting project root is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot delete project root"):
        await fm.delete("")


@pytest.mark.asyncio
async def test_rename_file(temp_project):
    """Test renaming a file."""
    fm = FileManager(str(temp_project))

    await fm.rename("README.md", "README.txt")

    assert not (temp_project / "README.md").exists()
    assert (temp_project / "README.txt").exists()


@pytest.mark.asyncio
async def test_rename_move_file(temp_project):
    """Test moving a file to different directory."""
    fm = FileManager(str(temp_project))

    await fm.rename("README.md", "src/README.md")

    assert not (temp_project / "README.md").exists()
    assert (temp_project / "src" / "README.md").exists()


@pytest.mark.asyncio
async def test_rename_not_found(temp_project):
    """Test renaming non-existent file."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.rename("nonexistent.txt", "new.txt")


@pytest.mark.asyncio
async def test_rename_destination_exists(temp_project):
    """Test renaming to existing path."""
    fm = FileManager(str(temp_project))

    # Create destination file
    await fm.create_file("existing.txt")

    with pytest.raises(FileExistsError):
        await fm.rename("README.md", "existing.txt")


def test_get_file_info(temp_project):
    """Test getting file info."""
    fm = FileManager(str(temp_project))

    info = fm.get_file_info("README.md")

    assert info["name"] == "README.md"
    assert info["path"] == "README.md"
    assert info["type"] == "file"
    assert info["size"] == len("# Test Project")
    assert "modified" in info


def test_get_file_info_directory(temp_project):
    """Test getting directory info."""
    fm = FileManager(str(temp_project))

    info = fm.get_file_info("src")

    assert info["name"] == "src"
    assert info["type"] == "directory"
    assert info["size"] is None


def test_exists(temp_project):
    """Test checking if path exists."""
    fm = FileManager(str(temp_project))

    assert fm.exists("README.md") is True
    assert fm.exists("src") is True
    assert fm.exists("nonexistent") is False


def test_exists_path_traversal(temp_project):
    """Test exists with path traversal returns False."""
    fm = FileManager(str(temp_project))

    assert fm.exists("../../../etc/passwd") is False


# Copy item tests
@pytest.mark.asyncio
async def test_copy_file(temp_project):
    """Test copying a file."""
    fm = FileManager(str(temp_project))

    await fm.copy_item("README.md", "README_copy.md")

    # Original should still exist
    assert (temp_project / "README.md").exists()
    # Copy should exist with same content
    assert (temp_project / "README_copy.md").exists()
    assert (temp_project / "README_copy.md").read_text() == "# Test Project"


@pytest.mark.asyncio
async def test_copy_file_to_subdirectory(temp_project):
    """Test copying a file to a subdirectory."""
    fm = FileManager(str(temp_project))

    await fm.copy_item("README.md", "src/README.md")

    assert (temp_project / "README.md").exists()
    assert (temp_project / "src" / "README.md").exists()


@pytest.mark.asyncio
async def test_copy_directory(temp_project):
    """Test copying a directory with contents."""
    fm = FileManager(str(temp_project))

    await fm.copy_item("src", "src_backup")

    assert (temp_project / "src").exists()
    assert (temp_project / "src_backup").exists()
    assert (temp_project / "src_backup" / "main.py").exists()
    assert (temp_project / "src_backup" / "utils" / "helpers.py").exists()


@pytest.mark.asyncio
async def test_copy_nonexistent(temp_project):
    """Test copying non-existent file."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.copy_item("nonexistent.txt", "copy.txt")


@pytest.mark.asyncio
async def test_copy_destination_exists(temp_project):
    """Test copying to existing path."""
    fm = FileManager(str(temp_project))

    # Create destination file
    await fm.create_file("existing.txt")

    with pytest.raises(FileExistsError):
        await fm.copy_item("README.md", "existing.txt")


@pytest.mark.asyncio
async def test_copy_creates_parent_directories(temp_project):
    """Test that copy creates parent directories if needed."""
    fm = FileManager(str(temp_project))

    await fm.copy_item("README.md", "deep/nested/dir/README.md")

    assert (temp_project / "deep" / "nested" / "dir" / "README.md").exists()


# Move item tests
@pytest.mark.asyncio
async def test_move_file(temp_project):
    """Test moving a file."""
    fm = FileManager(str(temp_project))

    await fm.move_item("README.md", "README_moved.md")

    assert not (temp_project / "README.md").exists()
    assert (temp_project / "README_moved.md").exists()
    assert (temp_project / "README_moved.md").read_text() == "# Test Project"


@pytest.mark.asyncio
async def test_move_file_to_subdirectory(temp_project):
    """Test moving a file to a subdirectory."""
    fm = FileManager(str(temp_project))

    await fm.move_item("README.md", "tests/README.md")

    assert not (temp_project / "README.md").exists()
    assert (temp_project / "tests" / "README.md").exists()


@pytest.mark.asyncio
async def test_move_directory(temp_project):
    """Test moving a directory with contents."""
    fm = FileManager(str(temp_project))

    await fm.move_item("src/utils", "tests/utils")

    assert not (temp_project / "src" / "utils").exists()
    assert (temp_project / "tests" / "utils").exists()
    assert (temp_project / "tests" / "utils" / "helpers.py").exists()


@pytest.mark.asyncio
async def test_move_nonexistent(temp_project):
    """Test moving non-existent file."""
    fm = FileManager(str(temp_project))

    with pytest.raises(FileNotFoundError):
        await fm.move_item("nonexistent.txt", "moved.txt")


@pytest.mark.asyncio
async def test_move_destination_exists(temp_project):
    """Test moving to existing path."""
    fm = FileManager(str(temp_project))

    await fm.create_file("existing.txt")

    with pytest.raises(FileExistsError):
        await fm.move_item("README.md", "existing.txt")


@pytest.mark.asyncio
async def test_move_project_root_prevented(temp_project):
    """Test that moving project root is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot move project root"):
        await fm.move_item("", "somewhere")


@pytest.mark.asyncio
async def test_move_creates_parent_directories(temp_project):
    """Test that move creates parent directories if needed."""
    fm = FileManager(str(temp_project))

    await fm.move_item("README.md", "new/nested/path/README.md")

    assert not (temp_project / "README.md").exists()
    assert (temp_project / "new" / "nested" / "path" / "README.md").exists()


@pytest.mark.asyncio
async def test_copy_directory_into_itself_prevented(temp_project):
    """Test that copying a directory into itself is prevented (would cause infinite loop)."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot copy a folder into itself"):
        await fm.copy_item("src", "src/backup")


@pytest.mark.asyncio
async def test_copy_directory_into_descendant_prevented(temp_project):
    """Test that copying a directory into a descendant is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot copy a folder into itself"):
        await fm.copy_item("src", "src/utils/backup")


@pytest.mark.asyncio
async def test_move_directory_into_itself_prevented(temp_project):
    """Test that moving a directory into itself is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot move a folder into itself"):
        await fm.move_item("src", "src/moved")


@pytest.mark.asyncio
async def test_move_directory_into_descendant_prevented(temp_project):
    """Test that moving a directory into a descendant is prevented."""
    fm = FileManager(str(temp_project))

    with pytest.raises(ValueError, match="Cannot move a folder into itself"):
        await fm.move_item("src", "src/utils/moved")
