#!/usr/bin/env python3
"""
Tests for tree_sitter_analyzer.core.parser module.

This module tests the Parser class which handles Tree-sitter parsing
operations in the new architecture.
"""

import pytest
import tempfile
import os
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from typing import Optional, NamedTuple

from tree_sitter_analyzer.core.parser import Parser, ParseResult


class TestParser:
    """Test cases for Parser class"""

    @pytest.fixture
    def parser(self) -> Parser:
        """Create a Parser instance for testing"""
        return Parser()

    @pytest.fixture
    def sample_java_file(self) -> str:
        """Create a temporary Java file for testing"""
        content = '''
public class TestClass {
    private String name;
    
    public TestClass(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}
'''
        with tempfile.NamedTemporaryFile(mode='w', suffix='.java', delete=False) as f:
            f.write(content)
            return f.name

    @pytest.fixture
    def sample_python_file(self) -> str:
        """Create a temporary Python file for testing"""
        content = '''
class TestClass:
    def __init__(self, name: str):
        self.name = name
    
    def get_name(self) -> str:
        return self.name
'''
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
            f.write(content)
            return f.name

    def test_parser_initialization(self, parser: Parser) -> None:
        """Test Parser initialization"""
        assert parser is not None
        assert hasattr(parser, 'parse_file')
        assert hasattr(parser, 'parse_code')

    def test_parse_file_java_success(self, parser: Parser, sample_java_file: str) -> None:
        """Test successful Java file parsing"""
        try:
            result = parser.parse_file(sample_java_file, 'java')
            
            assert isinstance(result, ParseResult)
            assert result.tree is not None
            assert result.source_code is not None
            assert result.language == 'java'
            assert result.file_path == sample_java_file
            assert result.success is True
            assert result.error_message is None
            
        finally:
            os.unlink(sample_java_file)

    def test_parse_file_python_success(self, parser: Parser, sample_python_file: str) -> None:
        """Test successful Python file parsing"""
        try:
            result = parser.parse_file(sample_python_file, 'python')
            
            assert isinstance(result, ParseResult)
            assert result.tree is not None
            assert result.source_code is not None
            assert result.language == 'python'
            assert result.file_path == sample_python_file
            assert result.success is True
            
        finally:
            os.unlink(sample_python_file)

    def test_parse_file_with_path_object(self, parser: Parser, sample_java_file: str) -> None:
        """Test file parsing with Path object"""
        try:
            path_obj = Path(sample_java_file)
            result = parser.parse_file(path_obj, 'java')
            
            assert isinstance(result, ParseResult)
            assert result.success is True
            assert result.file_path == str(path_obj)
            
        finally:
            os.unlink(sample_java_file)

    def test_parse_file_nonexistent(self, parser: Parser) -> None:
        """Test parsing of non-existent file"""
        result = parser.parse_file('/nonexistent/file.java', 'java')
        
        assert isinstance(result, ParseResult)
        assert result.success is False
        assert result.tree is None
        assert result.error_message is not None
        assert 'not found' in result.error_message.lower() or 'no such file' in result.error_message.lower()

    def test_parse_file_unsupported_language(self, parser: Parser, sample_java_file: str) -> None:
        """Test parsing with unsupported language"""
        try:
            result = parser.parse_file(sample_java_file, 'unsupported_language')
            
            assert isinstance(result, ParseResult)
            assert result.success is False
            assert result.error_message is not None
            
        finally:
            os.unlink(sample_java_file)

    def test_parse_code_java_success(self, parser: Parser) -> None:
        """Test successful Java code parsing"""
        java_code = '''
public class TestClass {
    public void testMethod() {
        System.out.println("Hello");
    }
}
'''
        result = parser.parse_code(java_code, 'java')
        
        assert isinstance(result, ParseResult)
        assert result.tree is not None
        assert result.source_code == java_code
        assert result.language == 'java'
        assert result.success is True

    def test_parse_code_python_success(self, parser: Parser) -> None:
        """Test successful Python code parsing"""
        python_code = '''
def test_function():
    print("Hello")
    return True
'''
        result = parser.parse_code(python_code, 'python')
        
        assert isinstance(result, ParseResult)
        assert result.tree is not None
        assert result.source_code == python_code
        assert result.language == 'python'
        assert result.success is True

    def test_parse_code_with_filename(self, parser: Parser) -> None:
        """Test code parsing with filename"""
        code = 'def test(): pass'
        result = parser.parse_code(code, 'python', filename='test.py')
        
        assert isinstance(result, ParseResult)
        assert result.file_path == 'test.py'

    def test_parse_code_empty_content(self, parser: Parser) -> None:
        """Test parsing of empty code content"""
        result = parser.parse_code('', 'java')
        
        assert isinstance(result, ParseResult)
        assert result.source_code == ''
        # Empty content should still parse successfully
        assert result.success is True

    def test_parse_code_malformed_content(self, parser: Parser) -> None:
        """Test parsing of malformed code content"""
        malformed_java = '''
public class Test {
    // Missing closing brace
'''
        result = parser.parse_code(malformed_java, 'java')
        
        # Should still parse but may have errors in the tree
        assert isinstance(result, ParseResult)
        assert result.tree is not None
        assert result.success is True

    def test_is_language_supported(self, parser: Parser) -> None:
        """Test language support checking"""
        assert parser.is_language_supported('java') is True
        assert parser.is_language_supported('python') is True
        assert parser.is_language_supported('javascript') is True
        assert parser.is_language_supported('unsupported_language') is False

    def test_get_supported_languages(self, parser: Parser) -> None:
        """Test getting list of supported languages"""
        languages = parser.get_supported_languages()
        
        assert isinstance(languages, list)
        assert len(languages) > 0
        assert 'java' in languages
        assert 'python' in languages

    def test_validate_ast_valid_tree(self, parser: Parser) -> None:
        """Test AST validation with valid tree"""
        code = 'public class Test {}'
        result = parser.parse_code(code, 'java')
        
        if result.success and result.tree:
            is_valid = parser.validate_ast(result.tree)
            assert isinstance(is_valid, bool)

    def test_validate_ast_none_tree(self, parser: Parser) -> None:
        """Test AST validation with None tree"""
        is_valid = parser.validate_ast(None)
        assert is_valid is False

    def test_get_parse_errors(self, parser: Parser) -> None:
        """Test getting parse errors from tree"""
        # Parse malformed code that should have errors
        malformed_code = '''
public class Test {
    public void method( {
        // Missing closing parenthesis
    }
'''
        result = parser.parse_code(malformed_code, 'java')
        
        if result.success and result.tree:
            errors = parser.get_parse_errors(result.tree)
            assert isinstance(errors, list)


class TestParserErrorHandling:
    """Test error handling in Parser"""

    @pytest.fixture
    def parser(self) -> Parser:
        """Create a Parser instance for testing"""
        return Parser()

    @patch('pathlib.Path.exists')
    @patch('tree_sitter_analyzer.core.parser.EncodingManager.read_file_safe')
    def test_parse_file_permission_error(self, mock_read_safe: Mock, mock_exists: Mock, parser: Parser) -> None:
        """Test handling of file permission errors using mocks."""
        # Mock Path.exists() to return True, bypassing the file existence check
        mock_exists.return_value = True
        # Configure the mock to raise a PermissionError
        mock_read_safe.side_effect = PermissionError("Mocked permission denied")
        
        # Create a dummy file path for the test.
        dummy_path = "non_existent_dir/non_existent_file.java"
        
        # Execute the method
        result = parser.parse_file(dummy_path, 'java')
        
        # Assert that the failure was handled correctly
        assert result.success is False, "Expected success to be False on PermissionError"
        assert result.error_message is not None, "Expected an error message on failure"
        assert "permission denied" in result.error_message.lower(), "Error message should indicate a permission issue"

    def test_parse_file_encoding_error(self, parser: Parser) -> None:
        """Test handling of file encoding errors"""
        # Create file with invalid UTF-8 bytes
        with tempfile.NamedTemporaryFile(mode='wb', suffix='.java', delete=False) as f:
            f.write(b'public class Test { \xff\xfe }')  # Invalid UTF-8
            temp_path = f.name
        
        try:
            result = parser.parse_file(temp_path, 'java')
            # Should handle encoding errors gracefully
            assert isinstance(result, ParseResult)
            
        finally:
            os.unlink(temp_path)

    @patch('tree_sitter_analyzer.core.parser.loader.create_parser_safely')
    def test_parse_code_parser_creation_failure(self, mock_create_parser: Mock, parser: Parser) -> None:
        """Test handling of parser creation failure"""
        mock_create_parser.return_value = None
        
        result = parser.parse_code('test code', 'java')
        
        assert isinstance(result, ParseResult)
        assert result.success is False
        assert result.error_message is not None

    def test_parse_code_with_unicode(self, parser: Parser) -> None:
        """Test parsing code with Unicode characters"""
        unicode_code = '''
public class Test {
    // コメント with Unicode: 日本語
    String message = "こんにちは世界";
}
'''
        result = parser.parse_code(unicode_code, 'java')
        
        assert isinstance(result, ParseResult)
        assert result.success is True
        assert result.source_code == unicode_code

    def test_parse_very_large_file(self, parser: Parser) -> None:
        """Test parsing of very large code content"""
        # Create a large code string
        large_code = 'public class Test {\n'
        for i in range(1000):
            large_code += f'    public void method{i}() {{ }}\n'
        large_code += '}'
        
        result = parser.parse_code(large_code, 'java')
        
        assert isinstance(result, ParseResult)
        # Should handle large content
        assert result.success is True


class TestParserIntegration:
    """Integration tests for Parser"""

    @pytest.fixture
    def parser(self) -> Parser:
        """Create a Parser instance for testing"""
        return Parser()

    def test_parse_multiple_languages(self, parser: Parser) -> None:
        """Test parsing multiple different languages"""
        test_cases = [
            ('public class Test {}', 'java'),
            ('def test(): pass', 'python'),
            ('function test() {}', 'javascript'),
            ('fn test() {}', 'rust'),
        ]
        
        for code, language in test_cases:
            if parser.is_language_supported(language):
                result = parser.parse_code(code, language)
                assert isinstance(result, ParseResult)
                assert result.language == language

    def test_parse_complex_java_file(self, parser: Parser) -> None:
        """Test parsing complex Java file"""
        complex_java = '''
package com.example.test;

import java.util.*;
import java.io.*;

/**
 * Complex test class with various Java features
 */
public class ComplexTest extends BaseClass implements TestInterface {
    private static final String CONSTANT = "test";
    private List<String> items = new ArrayList<>();
    
    public ComplexTest() {
        super();
    }
    
    @Override
    public void interfaceMethod() {
        // Implementation
    }
    
    public <T> T genericMethod(T input) {
        return input;
    }
    
    public static void main(String[] args) {
        ComplexTest test = new ComplexTest();
        test.interfaceMethod();
    }
    
    private class InnerClass {
        void innerMethod() {}
    }
}
'''
        result = parser.parse_code(complex_java, 'java')
        
        assert isinstance(result, ParseResult)
        assert result.success is True
        assert result.tree is not None
        
        # Validate the tree structure
        is_valid = parser.validate_ast(result.tree)
        assert is_valid is True

    def test_parse_complex_python_file(self, parser: Parser) -> None:
        """Test parsing complex Python file"""
        complex_python = '''
#!/usr/bin/env python3
"""
Complex Python module with various features
"""

import os
import sys
from typing import List, Dict, Optional, Union
from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass
class DataClass:
    name: str
    value: int = 0

class BaseClass(ABC):
    @abstractmethod
    def abstract_method(self) -> None:
        pass

class ComplexClass(BaseClass):
    def __init__(self, name: str):
        self.name = name
        self._private_attr = "private"
    
    @property
    def name_property(self) -> str:
        return self.name
    
    @staticmethod
    def static_method() -> str:
        return "static"
    
    @classmethod
    def class_method(cls) -> 'ComplexClass':
        return cls("default")
    
    def abstract_method(self) -> None:
        print(f"Implementation for {self.name}")
    
    async def async_method(self) -> None:
        await asyncio.sleep(1)

def function_with_decorators():
    @decorator
    def inner_function():
        pass
    return inner_function

if __name__ == "__main__":
    obj = ComplexClass("test")
    obj.abstract_method()
'''
        result = parser.parse_code(complex_python, 'python')
        
        assert isinstance(result, ParseResult)
        assert result.success is True
        assert result.tree is not None

    def test_parse_result_namedtuple_properties(self, parser: Parser) -> None:
        """Test ParseResult NamedTuple properties"""
        code = 'public class Test {}'
        result = parser.parse_code(code, 'java')
        
        # Test NamedTuple properties
        assert hasattr(result, 'tree')
        assert hasattr(result, 'source_code')
        assert hasattr(result, 'language')
        assert hasattr(result, 'file_path')
        assert hasattr(result, 'success')
        assert hasattr(result, 'error_message')
        
        # Test immutability (NamedTuple should be immutable)
        with pytest.raises(AttributeError):
            result.language = 'python'  # Should raise AttributeError