"""Tests for the refactor tool."""

import os
import tempfile
import pytest

from hanzo_mcp.tools.refactor import RefactorTool, create_refactor_tool


class TestRefactorTool:
    """Tests for RefactorTool class."""

    @pytest.fixture
    def tool(self):
        """Create a refactor tool instance."""
        return create_refactor_tool()

    @pytest.fixture
    def temp_python_file(self):
        """Create a temporary Python file for testing."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
            f.write('''def old_function(x, y):
    result = x + y
    return result

def caller():
    value = old_function(1, 2)
    return value

class MyClass:
    def method(self):
        return old_function(3, 4)
''')
            temp_path = f.name

        yield temp_path

        # Cleanup
        if os.path.exists(temp_path):
            os.unlink(temp_path)

    @pytest.fixture
    def temp_js_file(self):
        """Create a temporary JavaScript file for testing."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
            f.write('''function oldFunction(x, y) {
    const result = x + y;
    return result;
}

function caller() {
    const value = oldFunction(1, 2);
    return value;
}
''')
            temp_path = f.name

        yield temp_path

        if os.path.exists(temp_path):
            os.unlink(temp_path)

    def test_tool_creation(self, tool):
        """Test that the tool can be created."""
        assert tool is not None
        assert tool.name == "refactor"

    def test_tool_has_description(self, tool):
        """Test that the tool has a description."""
        assert tool.description is not None
        assert "refactor" in tool.description.lower()

    @pytest.mark.asyncio
    async def test_invalid_action(self, tool):
        """Test error handling for invalid action."""
        result = await tool.run(action="invalid_action", file="test.py")
        assert result.data["error"] is not None
        assert "Invalid action" in result.data["error"]

    @pytest.mark.asyncio
    async def test_file_not_found(self, tool):
        """Test error handling for non-existent file."""
        result = await tool.run(action="rename", file="/nonexistent/file.py", line=1, column=0, new_name="new")
        assert "error" in result.data
        assert "not found" in result.data["error"].lower()

    @pytest.mark.asyncio
    async def test_find_references_missing_args(self, tool, temp_python_file):
        """Test find_references without required arguments."""
        result = await tool.run(action="find_references", file=temp_python_file)
        assert result.data["success"] is False
        assert "required" in result.data["errors"][0].lower()

    @pytest.mark.asyncio
    async def test_find_references(self, tool, temp_python_file):
        """Test finding references to a symbol."""
        result = await tool.run(
            action="find_references",
            file=temp_python_file,
            line=1,
            column=4,  # 'old_function'
        )
        assert result.data["success"] is True
        assert result.data["action"] == "find_references"
        assert len(result.data["changes"]) > 0

    @pytest.mark.asyncio
    async def test_rename_preview(self, tool, temp_python_file):
        """Test rename in preview mode."""
        result = await tool.run(
            action="rename",
            file=temp_python_file,
            line=1,
            column=4,  # 'old_function'
            new_name="new_function",
            preview=True,
        )
        assert result.data["success"] is True
        assert result.data["action"] == "rename"
        assert "preview" in result.data
        assert len(result.data["preview"]) > 0

        # Verify file wasn't modified
        with open(temp_python_file, "r") as f:
            content = f.read()
        assert "old_function" in content
        assert "new_function" not in content

    @pytest.mark.asyncio
    async def test_rename_apply(self, tool, temp_python_file):
        """Test rename with actual application."""
        result = await tool.run(
            action="rename",
            file=temp_python_file,
            line=1,
            column=4,  # 'old_function'
            new_name="new_function",
            preview=False,
        )
        assert result.data["success"] is True
        assert result.data["action"] == "rename"
        assert result.data["changes_applied"] > 0

        # Verify file was modified
        with open(temp_python_file, "r") as f:
            content = f.read()
        assert "new_function" in content
        # All occurrences should be renamed
        assert "old_function" not in content

    @pytest.mark.asyncio
    async def test_extract_function_missing_name(self, tool, temp_python_file):
        """Test extract_function without new_name."""
        result = await tool.run(
            action="extract_function",
            file=temp_python_file,
            start_line=2,
            end_line=3,
        )
        assert result.data["success"] is False
        assert "new_name" in result.data["errors"][0].lower()

    @pytest.mark.asyncio
    async def test_extract_function_preview(self, tool, temp_python_file):
        """Test extract_function in preview mode."""
        result = await tool.run(
            action="extract_function",
            file=temp_python_file,
            start_line=2,
            end_line=3,
            new_name="compute_result",
            preview=True,
        )
        assert result.data["success"] is True
        assert result.data["action"] == "extract_function"
        assert "preview" in result.data
        assert len(result.data["preview"]) > 0

    @pytest.mark.asyncio
    async def test_extract_variable_missing_args(self, tool, temp_python_file):
        """Test extract_variable without required arguments."""
        result = await tool.run(
            action="extract_variable",
            file=temp_python_file,
        )
        assert result.data["success"] is False

    @pytest.mark.asyncio
    async def test_inline_missing_args(self, tool, temp_python_file):
        """Test inline without required arguments."""
        result = await tool.run(
            action="inline",
            file=temp_python_file,
        )
        assert result.data["success"] is False

    @pytest.mark.asyncio
    async def test_move_missing_target(self, tool, temp_python_file):
        """Test move without target_file."""
        result = await tool.run(
            action="move",
            file=temp_python_file,
            line=1,
        )
        assert result.data["success"] is False
        assert "target_file" in result.data["errors"][0].lower()

    @pytest.mark.asyncio
    async def test_organize_imports_python(self, tool):
        """Test organize imports for Python file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
            f.write('''import os
from typing import List
import sys
from collections import defaultdict
import json

def main():
    pass
''')
            temp_path = f.name

        try:
            result = await tool.run(
                action="organize_imports",
                file=temp_path,
                preview=False,
            )
            assert result.data["success"] is True

            with open(temp_path, "r") as f:
                content = f.read()

            # Imports should be sorted
            lines = content.split("\n")
            import_lines = [l for l in lines if l.startswith("import ")]
            from_lines = [l for l in lines if l.startswith("from ")]

            # Check imports are sorted
            assert import_lines == sorted(import_lines, key=str.lower)
            assert from_lines == sorted(from_lines, key=str.lower)

        finally:
            if os.path.exists(temp_path):
                os.unlink(temp_path)


class TestRefactorToolHelpers:
    """Tests for RefactorTool helper methods."""

    @pytest.fixture
    def tool(self):
        """Create a refactor tool instance."""
        return RefactorTool()

    def test_get_identifier_at_basic(self, tool):
        """Test getting identifier at position."""
        line = "def my_function(arg1, arg2):"
        assert tool._get_identifier_at(line, 4) == "my_function"
        assert tool._get_identifier_at(line, 16) == "arg1"
        assert tool._get_identifier_at(line, 23) == "arg2"

    def test_get_identifier_at_edge_cases(self, tool):
        """Test edge cases for identifier detection."""
        # Empty line
        assert tool._get_identifier_at("", 0) is None

        # Column out of range
        assert tool._get_identifier_at("hello", 100) == "hello"

        # No identifier at position
        assert tool._get_identifier_at("    ", 2) is None

    def test_get_indentation(self, tool):
        """Test indentation detection."""
        assert tool._get_indentation("def foo():") == ""
        assert tool._get_indentation("    return x") == "    "
        assert tool._get_indentation("\t\treturn y") == "\t\t"
        assert tool._get_indentation("") == ""

    def test_get_language(self, tool):
        """Test language detection from file extension."""
        assert tool._get_language("test.py") == "python"
        assert tool._get_language("test.js") == "javascript"
        assert tool._get_language("test.ts") == "typescript"
        assert tool._get_language("test.go") == "go"
        assert tool._get_language("test.rs") == "rust"
        assert tool._get_language("test.unknown") == "unknown"

    def test_find_used_variables(self, tool):
        """Test finding used variables in code."""
        code = "result = x + y"
        vars = tool._find_used_variables(code, "python")
        assert "x" in vars
        assert "y" in vars
        assert "result" in vars

    def test_find_defined_variables_python(self, tool):
        """Test finding defined variables in Python."""
        code = "x = 10\ny = 20"
        vars = tool._find_defined_variables(code, "python")
        assert "x" in vars
        assert "y" in vars

    def test_find_defined_variables_javascript(self, tool):
        """Test finding defined variables in JavaScript."""
        code = "const x = 10;\nlet y = 20;"
        vars = tool._find_defined_variables(code, "javascript")
        assert "x" in vars
        assert "y" in vars

    def test_build_function_python(self, tool):
        """Test building Python function."""
        func = tool._build_function("my_func", ["x", "y"], "return x + y", "python", "")
        assert "def my_func(x, y):" in func
        assert "return x + y" in func

    def test_build_function_javascript(self, tool):
        """Test building JavaScript function."""
        func = tool._build_function("myFunc", ["x", "y"], "return x + y;", "javascript", "")
        assert "function myFunc(x, y)" in func
        assert "return x + y;" in func

    def test_build_variable_declaration_python(self, tool):
        """Test building Python variable declaration."""
        decl = tool._build_variable_declaration("x", "10 + 20", "python", "    ")
        assert decl == "    x = 10 + 20"

    def test_build_variable_declaration_javascript(self, tool):
        """Test building JavaScript variable declaration."""
        decl = tool._build_variable_declaration("x", "10 + 20", "javascript", "  ")
        assert decl == "  const x = 10 + 20;"


# Test factory function
def test_create_refactor_tool():
    """Test the factory function."""
    tool = create_refactor_tool()
    assert isinstance(tool, RefactorTool)
    assert tool.name == "refactor"
