import os
import pytest
from scripts import py_struct_tools
from src import mcp_client

@pytest.fixture
def temp_py_file(tmp_path):
    p = tmp_path / "sample.py"
    content = """class MyClass:
 \"\"\"Docstring.\"\"\"
 def method1(self):
  print("m1")

def top_func():
 \"\"\"Top doc.\"\"\"
 print("top")
"""
    p.write_text(content, encoding="utf-8")
    return str(p)

def test_find_definition_range():
    source = """class A:
 def m(self): pass
def f(): pass
"""
    assert py_struct_tools.find_definition_range(source, "A") == (1, 2)
    assert py_struct_tools.find_definition_range(source, "A.m") == (2, 2)
    assert py_struct_tools.find_definition_range(source, "f") == (3, 3)
    assert py_struct_tools.find_definition_range(source, "nonexistent") is None

def test_shift_indentation():
    payload = "def f():\n  print('hi')" # 2-space
    shifted = py_struct_tools.shift_indentation(payload, 1)
    assert shifted == " def f():\n  print('hi')" # wait, shift_indentation strips min and prepends.
    
    # Let's re-test shift_indentation logic
    # Original:
    # line 1: 'def f():' (0 indent)
    # line 2: '  print('hi')' (2 indent)
    # min_indent = 0
    # Prepend 1 space:
    # ' def f():'
    # '   print('hi')'
    
    # If payload was:
    #   def f():
    #     print('hi')
    # min_indent = 2
    # target_depth = 1
    # ' def f():'
    # '  print('hi')'
    
    payload2 = "  def f():\n    print('hi')"
    shifted2 = py_struct_tools.shift_indentation(payload2, 1)
    assert shifted2 == " def f():\n  print('hi')"

def test_py_remove_def(temp_py_file):
    err = py_struct_tools.py_remove_def(temp_py_file, "MyClass.method1")
    assert err == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
    assert "def method1" not in content
    assert "class MyClass" in content

def test_py_add_def(temp_py_file):
    new_code = "def method2(self):\n print('m2')"
    err = py_struct_tools.py_add_def(temp_py_file, "MyClass", new_code, "after", "method1")
    assert err == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
    assert "def method2" in content
    # Check 1-space indentation
    assert " def method2(self):" in content

def test_py_region_wrap(temp_py_file):
    err = py_struct_tools.py_region_wrap(temp_py_file, 6, 8, "MyRegion")
    assert err == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
    assert "#region: MyRegion" in content
    assert "#endregion: MyRegion" in content

def test_mcp_dispatch_integration(temp_py_file):
    # Mock allowlist
    mcp_client.configure([{"path": temp_py_file}])
    
    # Test py_remove_def
    result = mcp_client.dispatch("py_remove_def", {"path": temp_py_file, "name": "top_func"})
    assert result == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
        assert "def top_func" not in content

    # Test py_add_def (module level top)
    result = mcp_client.dispatch("py_add_def", {
        "path": temp_py_file, 
        "name": "", 
        "new_content": "def head_func():\n print('head')",
        "anchor_type": "top"
    })
    assert result == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
        assert content.startswith("def head_func")

    # Test py_add_def (class bottom)
    result = mcp_client.dispatch("py_add_def", {
        "path": temp_py_file,
        "name": "MyClass",
        "new_content": "def tail_method(self):\n print('tail')",
        "anchor_type": "bottom"
    })
    assert result == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
        assert "def tail_method" in content
        assert " def tail_method(self):" in content # Check indent

    # Test py_move_def (cross-file simulated with same file)
    # We move method1 to after tail_method
    result = mcp_client.dispatch("py_move_def", {
        "src_path": temp_py_file,
        "dest_path": temp_py_file,
        "name": "MyClass.method1",
        "dest_name": "MyClass",
        "anchor_type": "after",
        "anchor_symbol": "tail_method"
    })
    assert result == ""
    with open(temp_py_file, 'r') as f:
        content = f.read()
        # method1 should now be AFTER tail_method
        assert content.find("def method1") > content.find("def tail_method")

def test_mcp_dispatch_errors(temp_py_file):
    mcp_client.configure([{"path": temp_py_file}])
    
    # Non-existent symbol
    result = mcp_client.dispatch("py_remove_def", {"path": temp_py_file, "name": "NoSuchSymbol"})
    assert "ERROR" in result or "not found" in result

    # Denied path
    result = mcp_client.dispatch("py_remove_def", {"path": "C:/windows/system32/cmd.exe", "name": "foo"})
    assert "ACCESS DENIED" in result
