"""
Tests for the refactor tool functionality
"""

import json
from unittest.mock import MagicMock, patch

import pytest

from tools.refactor import RefactorTool
from utils.file_utils import read_file_content


class TestRefactorTool:
    """Test suite for the refactor tool"""

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

    @pytest.fixture
    def mock_model_response(self):
        """Create a mock model response with valid JSON"""

        def _create_response(content=None):
            if content is None:
                content = json.dumps(
                    {
                        "refactor_opportunities": [
                            {
                                "id": "refactor-001",
                                "type": "codesmells",
                                "severity": "high",
                                "file": "/test/file.py",
                                "start_line": 10,
                                "end_line": 25,
                                "context_start_text": "def long_method():",
                                "context_end_text": "    return result",
                                "issue": "Method too long with multiple responsibilities",
                                "suggestion": "Extract helper methods",
                                "rationale": "Improves readability and maintainability",
                                "code_to_replace": "# original code",
                                "replacement_code_snippet": "# refactored code",
                                "new_code_snippets": [],
                            }
                        ],
                        "priority_sequence": ["refactor-001"],
                        "next_actions_for_claude": [],
                    }
                )

            from unittest.mock import Mock

            return Mock(
                content=content,
                usage={"input_tokens": 100, "output_tokens": 200, "total_tokens": 300},
                model_name="test-model",
                metadata={"finish_reason": "STOP"},
            )

        return _create_response

    def test_get_name(self, refactor_tool):
        """Test that the tool returns the correct name"""
        assert refactor_tool.get_name() == "refactor"

    def test_get_description(self, refactor_tool):
        """Test that the tool returns a comprehensive description"""
        description = refactor_tool.get_description()
        assert "INTELLIGENT CODE REFACTORING" in description
        assert "codesmells" in description
        assert "decompose" in description
        assert "modernize" in description
        assert "organization" in description

    def test_get_input_schema(self, refactor_tool):
        """Test that the input schema includes all required fields"""
        schema = refactor_tool.get_input_schema()

        assert schema["type"] == "object"
        assert "files" in schema["properties"]
        assert "prompt" in schema["properties"]
        assert "refactor_type" in schema["properties"]

        # Check refactor_type enum values
        refactor_enum = schema["properties"]["refactor_type"]["enum"]
        expected_types = ["codesmells", "decompose", "modernize", "organization"]
        assert all(rt in refactor_enum for rt in expected_types)

    def test_language_detection_python(self, refactor_tool):
        """Test language detection for Python files"""
        files = ["/test/file1.py", "/test/file2.py", "/test/utils.py"]
        language = refactor_tool.detect_primary_language(files)
        assert language == "python"

    def test_language_detection_javascript(self, refactor_tool):
        """Test language detection for JavaScript files"""
        files = ["/test/app.js", "/test/component.jsx", "/test/utils.js"]
        language = refactor_tool.detect_primary_language(files)
        assert language == "javascript"

    def test_language_detection_mixed(self, refactor_tool):
        """Test language detection for mixed language files"""
        files = ["/test/app.py", "/test/script.js", "/test/main.java"]
        language = refactor_tool.detect_primary_language(files)
        assert language == "mixed"

    def test_language_detection_unknown(self, refactor_tool):
        """Test language detection for unknown file types"""
        files = ["/test/data.txt", "/test/config.json"]
        language = refactor_tool.detect_primary_language(files)
        assert language == "unknown"

    def test_language_specific_guidance_python(self, refactor_tool):
        """Test language-specific guidance for Python modernization"""
        guidance = refactor_tool.get_language_specific_guidance("python", "modernize")
        assert "f-strings" in guidance
        assert "dataclasses" in guidance
        assert "type hints" in guidance

    def test_language_specific_guidance_javascript(self, refactor_tool):
        """Test language-specific guidance for JavaScript modernization"""
        guidance = refactor_tool.get_language_specific_guidance("javascript", "modernize")
        assert "async/await" in guidance
        assert "destructuring" in guidance
        assert "arrow functions" in guidance

    def test_language_specific_guidance_unknown(self, refactor_tool):
        """Test language-specific guidance for unknown languages"""
        guidance = refactor_tool.get_language_specific_guidance("unknown", "modernize")
        assert guidance == ""

    @pytest.mark.asyncio
    async def test_execute_basic_refactor(self, refactor_tool, mock_model_response):
        """Test basic refactor tool execution"""
        with patch.object(refactor_tool, "get_model_provider") as mock_get_provider:
            mock_provider = MagicMock()
            mock_provider.get_provider_type.return_value = MagicMock(value="test")
            mock_provider.supports_thinking_mode.return_value = False
            mock_provider.generate_content.return_value = mock_model_response()
            mock_get_provider.return_value = mock_provider

            # Mock file processing
            with patch.object(refactor_tool, "_prepare_file_content_for_prompt") as mock_prepare:
                mock_prepare.return_value = ("def test(): pass", ["/test/file.py"], None)

                result = await refactor_tool.execute(
                    {
                        "files": ["/test/file.py"],
                        "prompt": "Find code smells in this Python code",
                        "refactor_type": "codesmells",
                    }
                )

                assert len(result) == 1
                output = json.loads(result[0].text)
                assert output["status"] == "success"
                # The format_response method adds markdown instructions, so content_type should be "markdown"
                # It could also be "json" or "text" depending on the response format
                assert output["content_type"] in ["json", "text", "markdown"]

    @pytest.mark.asyncio
    async def test_execute_with_style_guide(self, refactor_tool, mock_model_response):
        """Test refactor tool execution with style guide examples"""
        with patch.object(refactor_tool, "get_model_provider") as mock_get_provider:
            mock_provider = MagicMock()
            mock_provider.get_provider_type.return_value = MagicMock(value="test")
            mock_provider.supports_thinking_mode.return_value = False
            mock_provider.generate_content.return_value = mock_model_response()
            mock_get_provider.return_value = mock_provider

            # Mock file processing
            with patch.object(refactor_tool, "_prepare_file_content_for_prompt") as mock_prepare:
                mock_prepare.return_value = ("def example(): pass", ["/test/file.py"], None)

            with patch.object(refactor_tool, "_process_style_guide_examples") as mock_style:
                mock_style.return_value = ("# style guide content", "")

                result = await refactor_tool.execute(
                    {
                        "files": ["/test/file.py"],
                        "prompt": "Modernize this code following our style guide",
                        "refactor_type": "modernize",
                        "style_guide_examples": ["/test/style_example.py"],
                    }
                )

                assert len(result) == 1
                output = json.loads(result[0].text)
                assert output["status"] == "success"

    def test_format_response_valid_json(self, refactor_tool):
        """Test response formatting with valid structured JSON"""
        valid_json_response = json.dumps(
            {
                "status": "refactor_analysis_complete",
                "refactor_opportunities": [
                    {
                        "id": "test-001",
                        "type": "codesmells",
                        "severity": "medium",
                        "file": "/test.py",
                        "start_line": 1,
                        "end_line": 5,
                        "context_start_text": "def test():",
                        "context_end_text": "    pass",
                        "issue": "Test issue",
                        "suggestion": "Test suggestion",
                        "rationale": "Test rationale",
                        "code_to_replace": "old code",
                        "replacement_code_snippet": "new code",
                    }
                ],
                "priority_sequence": ["test-001"],
                "next_actions_for_claude": [],
            }
        )

        # Create a mock request
        request = MagicMock()
        request.refactor_type = "codesmells"

        formatted = refactor_tool.format_response(valid_json_response, request)

        # Should contain the original response plus implementation instructions
        assert valid_json_response in formatted
        assert "MANDATORY NEXT STEPS" in formatted
        assert "Start executing the refactoring plan immediately" in formatted
        assert "MANDATORY: MUST start executing the refactor plan" in formatted

    def test_format_response_invalid_json(self, refactor_tool):
        """Test response formatting with invalid JSON - now handled by base tool"""
        invalid_response = "This is not JSON content"

        # Create a mock request
        request = MagicMock()
        request.refactor_type = "codesmells"

        formatted = refactor_tool.format_response(invalid_response, request)

        # Should contain the original response plus implementation instructions
        assert invalid_response in formatted
        assert "MANDATORY NEXT STEPS" in formatted
        assert "Start executing the refactoring plan immediately" in formatted

    def test_model_category(self, refactor_tool):
        """Test that the refactor tool uses EXTENDED_REASONING category"""
        from tools.models import ToolModelCategory

        category = refactor_tool.get_model_category()
        assert category == ToolModelCategory.EXTENDED_REASONING

    def test_default_temperature(self, refactor_tool):
        """Test that the refactor tool uses analytical temperature"""
        from config import TEMPERATURE_ANALYTICAL

        temp = refactor_tool.get_default_temperature()
        assert temp == TEMPERATURE_ANALYTICAL

    def test_format_response_more_refactor_required(self, refactor_tool):
        """Test that format_response handles more_refactor_required field"""
        more_refactor_response = json.dumps(
            {
                "status": "refactor_analysis_complete",
                "refactor_opportunities": [
                    {
                        "id": "refactor-001",
                        "type": "decompose",
                        "severity": "critical",
                        "file": "/test/file.py",
                        "start_line": 1,
                        "end_line": 10,
                        "context_start_text": "def test_function():",
                        "context_end_text": "    return True",
                        "issue": "Function too large",
                        "suggestion": "Break into smaller functions",
                        "rationale": "Improves maintainability",
                        "code_to_replace": "original code",
                        "replacement_code_snippet": "refactored code",
                        "new_code_snippets": [],
                    }
                ],
                "priority_sequence": ["refactor-001"],
                "next_actions_for_claude": [
                    {
                        "action_type": "EXTRACT_METHOD",
                        "target_file": "/test/file.py",
                        "source_lines": "1-10",
                        "description": "Extract method from large function",
                    }
                ],
                "more_refactor_required": True,
                "continuation_message": "Large codebase requires extensive refactoring across multiple files",
            }
        )

        # Create a mock request
        request = MagicMock()
        request.refactor_type = "decompose"

        formatted = refactor_tool.format_response(more_refactor_response, request)

        # Should contain the original response plus continuation instructions
        assert more_refactor_response in formatted
        assert "MANDATORY NEXT STEPS" in formatted
        assert "Start executing the refactoring plan immediately" in formatted
        assert "MANDATORY: MUST start executing the refactor plan" in formatted
        assert "AFTER IMPLEMENTING ALL ABOVE" in formatted  # Special instruction for more_refactor_required
        assert "continuation_id" in formatted


class TestFileUtilsLineNumbers:
    """Test suite for line numbering functionality in file_utils"""

    def test_read_file_content_with_line_numbers(self, project_path):
        """Test reading file content with line numbers enabled"""

        # Create a test file within the workspace
        temp_path = project_path / "test_file.py"
        with open(temp_path, "w") as f:
            f.write("def hello():\n    print('Hello')\n    return True")

        # Read with line numbers explicitly enabled
        content, tokens = read_file_content(str(temp_path), include_line_numbers=True)

        # Check that line numbers are present
        assert "1│ def hello():" in content
        assert "2│     print('Hello')" in content
        assert "3│     return True" in content
        assert "--- BEGIN FILE:" in content
        assert "--- END FILE:" in content

    def test_read_file_content_without_line_numbers(self, project_path):
        """Test reading file content with line numbers disabled"""

        # Create a test file within the workspace
        temp_path = project_path / "test_file.txt"
        with open(temp_path, "w") as f:
            f.write("Line 1\nLine 2\nLine 3")

        # Read with line numbers explicitly disabled
        content, tokens = read_file_content(str(temp_path), include_line_numbers=False)

        # Check that line numbers are NOT present
        assert "1│" not in content
        assert "Line 1" in content
        assert "Line 2" in content
        assert "--- BEGIN FILE:" in content

    def test_read_file_content_auto_detect_programming(self, project_path):
        """Test that auto-detection is OFF by default (backwards compatibility)"""

        # Create a test file within the workspace
        temp_path = project_path / "test_auto.py"
        with open(temp_path, "w") as f:
            f.write("import os\nprint('test')")

        # Read without specifying line numbers (should NOT auto-detect for backwards compatibility)
        content, tokens = read_file_content(str(temp_path))

        # Should NOT automatically add line numbers for .py files (default behavior)
        assert "1│" not in content
        assert "import os" in content
        assert "print('test')" in content

    def test_read_file_content_auto_detect_text(self, project_path):
        """Test auto-detection of line numbers for text files"""

        # Create a test file within the workspace
        temp_path = project_path / "test_auto.txt"
        with open(temp_path, "w") as f:
            f.write("This is a text file\nWith multiple lines")

        # Read without specifying line numbers (should auto-detect)
        content, tokens = read_file_content(str(temp_path))

        # Should NOT automatically add line numbers for .txt files
        assert "1│" not in content
        assert "This is a text file" in content

    def test_line_ending_normalization(self):
        """Test that different line endings are normalized consistently"""
        from utils.file_utils import _add_line_numbers, _normalize_line_endings

        # Test different line ending formats
        content_crlf = "Line 1\r\nLine 2\r\nLine 3"
        content_cr = "Line 1\rLine 2\rLine 3"
        content_lf = "Line 1\nLine 2\nLine 3"

        # All should normalize to the same result
        normalized_crlf = _normalize_line_endings(content_crlf)
        normalized_cr = _normalize_line_endings(content_cr)
        normalized_lf = _normalize_line_endings(content_lf)

        assert normalized_crlf == normalized_cr == normalized_lf
        assert normalized_lf == "Line 1\nLine 2\nLine 3"

        # Line numbering should work consistently
        numbered = _add_line_numbers(content_crlf)
        assert "   1│ Line 1" in numbered
        assert "   2│ Line 2" in numbered
        assert "   3│ Line 3" in numbered

    def test_detect_file_type(self):
        """Test file type detection"""
        from utils.file_utils import detect_file_type

        # Test programming language files
        assert detect_file_type("test.py") == "text"
        assert detect_file_type("test.js") == "text"
        assert detect_file_type("test.java") == "text"

        # Test image files
        assert detect_file_type("image.png") == "image"
        assert detect_file_type("photo.jpg") == "image"

        # Test binary files
        assert detect_file_type("program.exe") == "binary"
        assert detect_file_type("library.dll") == "binary"

    def test_should_add_line_numbers(self):
        """Test line number detection logic"""
        from utils.file_utils import should_add_line_numbers

        # NO files should get line numbers by default (backwards compatibility)
        assert not should_add_line_numbers("test.py")
        assert not should_add_line_numbers("app.js")
        assert not should_add_line_numbers("Main.java")
        assert not should_add_line_numbers("readme.txt")
        assert not should_add_line_numbers("data.csv")

        # Explicit override should work
        assert should_add_line_numbers("readme.txt", True)
        assert not should_add_line_numbers("test.py", False)

    def test_line_numbers_double_triple_digits(self, project_path):
        """Test line numbering with double and triple digit line numbers"""
        from utils.file_utils import _add_line_numbers

        # Create content with many lines to test double and triple digit formatting
        lines = []
        for i in range(1, 125):  # Lines 1-124 for testing up to triple digits
            if i < 10:
                lines.append(f"# Single digit line {i}")
            elif i < 100:
                lines.append(f"# Double digit line {i}")
            else:
                lines.append(f"# Triple digit line {i}")

        content = "\n".join(lines)
        numbered_content = _add_line_numbers(content)

        # Test single digit formatting (should be right-aligned with spaces)
        assert "   1│ # Single digit line 1" in numbered_content
        assert "   9│ # Single digit line 9" in numbered_content

        # Test double digit formatting (should be right-aligned)
        assert "  10│ # Double digit line 10" in numbered_content  # Line 10 has "double digit" content
        assert "  50│ # Double digit line 50" in numbered_content
        assert "  99│ # Double digit line 99" in numbered_content

        # Test triple digit formatting (should be right-aligned)
        assert " 100│ # Triple digit line 100" in numbered_content
        assert " 124│ # Triple digit line 124" in numbered_content

        # Verify consistent alignment - all line numbers should end with "│ "
        lines_with_numbers = numbered_content.split("\n")
        for line in lines_with_numbers:
            if "│" in line:
                # Find the pipe character position
                pipe_pos = line.find("│")
                # Ensure the character before pipe is a digit
                assert line[pipe_pos - 1].isdigit(), f"Line format issue: {line}"
                # Ensure the character after pipe is a space
                assert line[pipe_pos + 1] == " ", f"Line format issue: {line}"

    def test_line_numbers_with_file_reading(self, project_path):
        """Test line numbering through file reading with large file"""

        # Create a test file with 150 functions (600 total lines: 4 lines per function)
        temp_path = project_path / "large_test_file.py"
        with open(temp_path, "w") as f:
            for i in range(1, 151):  # Functions 1-150
                f.write(f"def function_{i}():\n")
                f.write(f"    # This is function number {i}\n")
                f.write(f"    return {i}\n")
                f.write("\n")

        # Read with line numbers enabled
        content, tokens = read_file_content(str(temp_path), include_line_numbers=True)

        # Calculate actual line numbers based on file structure (4 lines per function)
        # Function 1: lines 1-4, Function 2: lines 5-8, etc.
        # Line 1: def function_1():
        # Line 2: # This is function number 1
        # Line 3: return 1
        # Line 4: (empty)

        # Test various line number formats in the actual file content
        assert "   1│ def function_1():" in content

        # Function 13 starts at line 49 (12*4 + 1), so line 50 is "    # This is function number 13"
        assert "  50│     # This is function number 13" in content

        # Line 100 is actually an empty line after function 25 (line 99 was "return 25")
        assert " 100│ " in content  # Empty line

        # Line 99 is "return 25" from function 25
        assert "  99│     return 25" in content

        # Test more line numbers - line 147 is "return 37" from function 37
        assert " 147│     return 37" in content

        # Test that we have the final lines (600 total lines)
        assert " 599│     return 150" in content
        assert " 600│ " in content  # Final empty line

        # Verify the file structure is preserved
        assert "--- BEGIN FILE:" in content
        assert "--- END FILE:" in content
        assert str(temp_path) in content

    def test_line_numbers_large_files_22k_lines(self, project_path):
        """Test line numbering for very large files (22,500+ lines)"""
        from utils.file_utils import _add_line_numbers

        # Create content simulating a very large file with 25,000 lines
        lines = []
        for i in range(1, 25001):  # Lines 1-25000
            lines.append(f"// Large file line {i}")

        content = "\n".join(lines)
        numbered_content = _add_line_numbers(content)

        # Test that width dynamically adjusts to 5 digits for large files
        # Small line numbers should now have 5-digit width
        assert "    1│ // Large file line 1" in numbered_content
        assert "    9│ // Large file line 9" in numbered_content
        assert "   10│ // Large file line 10" in numbered_content
        assert "   99│ // Large file line 99" in numbered_content
        assert "  100│ // Large file line 100" in numbered_content
        assert "  999│ // Large file line 999" in numbered_content
        assert " 1000│ // Large file line 1000" in numbered_content
        assert " 9999│ // Large file line 9999" in numbered_content
        assert "10000│ // Large file line 10000" in numbered_content
        assert "22500│ // Large file line 22500" in numbered_content
        assert "25000│ // Large file line 25000" in numbered_content

        # Verify consistent alignment - all line numbers should end with "│ "
        lines_with_numbers = numbered_content.split("\n")
        for i, line in enumerate(lines_with_numbers[:100]):  # Check first 100 lines
            if "│" in line:
                pipe_pos = line.find("│")
                # For large files, should be 5-character width plus pipe
                assert line[pipe_pos - 1].isdigit(), f"Line {i+1} format issue: {line}"
                assert line[pipe_pos + 1] == " ", f"Line {i+1} format issue: {line}"

    def test_line_numbers_boundary_conditions(self):
        """Test line numbering at boundary conditions (9999 vs 10000 lines)"""
        from utils.file_utils import _add_line_numbers

        # Test exactly 9999 lines (should use 4-digit width)
        lines_9999 = [f"Line {i}" for i in range(1, 10000)]  # 9999 lines
        content_9999 = "\n".join(lines_9999)
        numbered_9999 = _add_line_numbers(content_9999)

        # Should use 4-digit format
        assert "   1│ Line 1" in numbered_9999
        assert "9999│ Line 9999" in numbered_9999

        # Test exactly 10000 lines (should use 5-digit width)
        lines_10000 = [f"Line {i}" for i in range(1, 10001)]  # 10000 lines
        content_10000 = "\n".join(lines_10000)
        numbered_10000 = _add_line_numbers(content_10000)

        # Should use 5-digit format
        assert "    1│ Line 1" in numbered_10000
        assert "10000│ Line 10000" in numbered_10000


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
