from __future__ import annotations

import os
import shutil
from unittest.mock import patch, MagicMock
import pytest

import actions.file_manager
from actions.file_manager import (
    handle_file_list,
    handle_file_read,
    handle_file_write,
    handle_file_delete,
    handle_file_rename,
    handle_file_create,
)


@pytest.fixture(autouse=True)
def setup_temp_base_path(tmp_path):
    """Override BASE_PATH with a temporary folder for isolated tests."""
    original_base = actions.file_manager.BASE_PATH
    actions.file_manager.BASE_PATH = str(tmp_path)
    yield tmp_path
    actions.file_manager.BASE_PATH = original_base


def test_verify_safe_path_blocks_path_traversal(tmp_path):
    # Try traversing outside BASE_PATH
    outside_path = os.path.join(str(tmp_path), "..", "outside_dir")
    
    res = handle_file_list({"path": outside_path})
    assert res["success"] is False
    assert "outside of base directory" in res["error"]

    res_read = handle_file_read({"path": outside_path})
    assert res_read["success"] is False
    assert "outside of base directory" in res_read["error"]


def test_handle_file_list_success(tmp_path):
    # Create test folders and files
    os.makedirs(os.path.join(str(tmp_path), "public"), exist_ok=True)
    with open(os.path.join(str(tmp_path), "test.txt"), "w", encoding="utf-8") as f:
        f.write("hello world")

    res = handle_file_list({"path": "/"})
    assert res["success"] is True
    
    items = res["items"]
    assert len(items) == 2
    
    names = [i["name"] for i in items]
    assert "public" in names
    assert "test.txt" in names
    
    txt_item = next(i for i in items if i["name"] == "test.txt")
    assert txt_item["type"] == "file"
    assert txt_item["size"] == "11 B"
    assert txt_item["modified_at"] is not None

    dir_item = next(i for i in items if i["name"] == "public")
    assert dir_item["type"] == "folder"
    assert dir_item["size"] is None


def test_handle_file_read_success_and_limits(tmp_path):
    file_path = os.path.join(str(tmp_path), "test.txt")
    
    # 1. Normal read
    with open(file_path, "w", encoding="utf-8") as f:
        f.write("content of text file")
    
    res = handle_file_read({"path": "test.txt"})
    assert res["success"] is True
    assert res["content"] == "content of text file"

    # 2. Over 2MB limit
    with patch("os.path.getsize", return_value=2 * 1024 * 1024 + 1):
        res = handle_file_read({"path": "test.txt"})
        assert res["success"] is False
        assert "exceeds maximum allowed size of 2MB" in res["error"]

    # 3. Binary detection (null byte)
    with open(file_path, "wb") as f:
        f.write(b"some binary \x00 content")
    res = handle_file_read({"path": "test.txt"})
    assert res["success"] is False
    assert "Binary files are not allowed" in res["error"]


def test_handle_file_write_atomic(tmp_path):
    file_path = os.path.join(str(tmp_path), "new_file.txt")
    
    res = handle_file_write({"path": "new_file.txt", "content": "atomic content"})
    assert res["success"] is True
    assert res["written"] is True
    assert os.path.exists(file_path)
    
    with open(file_path, "r", encoding="utf-8") as f:
        assert f.read() == "atomic content"

    # Limit check
    huge_content = "a" * (2 * 1024 * 1024 + 1)
    res_huge = handle_file_write({"path": "new_file.txt", "content": huge_content})
    assert res_huge["success"] is False
    assert "exceeds maximum allowed size of 2MB" in res_huge["error"]


def test_handle_file_delete(tmp_path):
    file_path = os.path.join(str(tmp_path), "test.txt")
    folder_path = os.path.join(str(tmp_path), "sub")
    
    with open(file_path, "w") as f: f.write("x")
    os.makedirs(folder_path, exist_ok=True)

    # Delete file
    res = handle_file_delete({"path": "test.txt"})
    assert res["success"] is True
    assert not os.path.exists(file_path)

    # Delete folder
    res_dir = handle_file_delete({"path": "sub"})
    assert res_dir["success"] is True
    assert not os.path.exists(folder_path)


def test_handle_file_rename(tmp_path):
    file_path = os.path.join(str(tmp_path), "old.txt")
    new_path = os.path.join(str(tmp_path), "new.txt")
    with open(file_path, "w") as f: f.write("test")

    # Rename
    res = handle_file_rename({"old_path": "old.txt", "new_name": "new.txt"})
    assert res["success"] is True
    assert not os.path.exists(file_path)
    assert os.path.exists(new_path)

    # Try renaming with folder path traversal
    res_invalid = handle_file_rename({"old_path": "new.txt", "new_name": "../traversal.txt"})
    assert res_invalid["success"] is False
    assert "Invalid file name format" in res_invalid["error"]


def test_handle_file_create(tmp_path):
    # Create file
    res = handle_file_create({"path": "created.txt", "type": "file"})
    assert res["success"] is True
    assert os.path.exists(os.path.join(str(tmp_path), "created.txt"))

    # Create folder
    res_dir = handle_file_create({"path": "created_dir", "type": "folder"})
    assert res_dir["success"] is True
    assert os.path.isdir(os.path.join(str(tmp_path), "created_dir"))
