"""
Python test generation logic for Storm Logger development
"""

import json
import ast
import re
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple


def generate_tests(
    file_path: str,
    test_type: str = "unit",
    coverage_target: int = 90,
    test_framework: str = "pytest"
) -> str:
    """
    Generate comprehensive test cases for Python code
    
    Args:
        file_path: Path to Python file to generate tests for
        test_type: "unit", "integration", "functional", "performance"
        coverage_target: Target coverage percentage (90-100)
        test_framework: "pytest", "unittest", "doctest"
        
    Returns:
        JSON string with generated test code and analysis
    """
    path = Path(file_path)
    
    if not path.exists():
        return json.dumps({"error": f"File {file_path} does not exist"}, indent=2)
    
    if not path.suffix == '.py':
        return json.dumps({"error": f"File {file_path} is not a Python file"}, indent=2)
    
    try:
        original_content = path.read_text(encoding='utf-8')
    except Exception as e:
        return json.dumps({"error": f"Cannot read file {file_path}: {str(e)}"}, indent=2)
    
    try:
        tree = ast.parse(original_content)
    except SyntaxError as e:
        return json.dumps({"error": f"Syntax error in {file_path}: {str(e)}"}, indent=2)
    
    generation = {
        "file_path": str(path),
        "test_type": test_type,
        "coverage_target": coverage_target,
        "test_framework": test_framework,
        "timestamp": "2025-01-18",
        "analysis": {},
        "test_cases": [],
        "fixtures": [],
        "mocks": [],
        "test_file_content": "",
        "coverage_analysis": {},
        "storm_logger_integration": {}
    }
    
    # Analyze the code structure
    analysis = analyze_code_structure(tree, original_content)
    generation["analysis"] = analysis
    
    # Generate test cases based on type
    if test_type == "unit":
        test_cases = generate_unit_tests(tree, original_content, test_framework)
    elif test_type == "integration":
        test_cases = generate_integration_tests(tree, original_content, test_framework)
    elif test_type == "functional":
        test_cases = generate_functional_tests(tree, original_content, test_framework)
    elif test_type == "performance":
        test_cases = generate_performance_tests(tree, original_content, test_framework)
    else:
        test_cases = generate_comprehensive_tests(tree, original_content, test_framework)
    
    generation["test_cases"] = test_cases
    
    # Generate fixtures and mocks
    generation["fixtures"] = generate_test_fixtures(tree, original_content, test_framework)
    generation["mocks"] = generate_test_mocks(tree, original_content, test_framework)
    
    # Generate complete test file
    generation["test_file_content"] = generate_complete_test_file(
        path, generation["test_cases"], generation["fixtures"], 
        generation["mocks"], test_framework
    )
    
    # Analyze coverage potential
    generation["coverage_analysis"] = analyze_coverage_potential(
        tree, generation["test_cases"], coverage_target
    )
    
    # Add Storm Logger integration
    generation["storm_logger_integration"] = generate_storm_logger_integration(
        tree, original_content, test_type
    )
    
    return json.dumps(generation, indent=2)


def analyze_code_structure(tree: ast.AST, content: str) -> Dict[str, Any]:
    """
    Analyze code structure to understand what needs testing
    
    Args:
        tree: AST tree
        content: File content
        
    Returns:
        Analysis results
    """
    analysis = {
        "functions": [],
        "classes": [],
        "imports": [],
        "complexity": {"total": 0, "average": 0},
        "test_priority": [],
        "patterns": []
    }
    
    # Analyze functions
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            func_analysis = analyze_function(node, content)
            analysis["functions"].append(func_analysis)
            analysis["complexity"]["total"] += func_analysis["complexity"]
            
            # Determine test priority
            if func_analysis["complexity"] > 5:
                analysis["test_priority"].append({
                    "name": node.name,
                    "priority": "high",
                    "reason": "High complexity"
                })
            elif func_analysis["public"]:
                analysis["test_priority"].append({
                    "name": node.name,
                    "priority": "medium",
                    "reason": "Public interface"
                })
        
        elif isinstance(node, ast.ClassDef):
            class_analysis = analyze_class(node, content)
            analysis["classes"].append(class_analysis)
        
        elif isinstance(node, (ast.Import, ast.ImportFrom)):
            import_analysis = analyze_import(node)
            analysis["imports"].append(import_analysis)
    
    # Calculate average complexity
    if analysis["functions"]:
        analysis["complexity"]["average"] = (
            analysis["complexity"]["total"] / len(analysis["functions"])
        )
    
    # Identify testing patterns
    analysis["patterns"] = identify_testing_patterns(tree, content)
    
    return analysis


def analyze_function(node: ast.FunctionDef, content: str) -> Dict[str, Any]:
    """
    Analyze a function for testing requirements
    
    Args:
        node: Function AST node
        content: File content
        
    Returns:
        Function analysis
    """
    analysis = {
        "name": node.name,
        "line": node.lineno,
        "parameters": len(node.args.args),
        "returns": node.returns is not None,
        "docstring": ast.get_docstring(node) is not None,
        "complexity": calculate_complexity(node),
        "public": not node.name.startswith('_'),
        "async": isinstance(node, ast.AsyncFunctionDef),
        "decorators": [d.id if isinstance(d, ast.Name) else str(d) for d in node.decorator_list],
        "test_scenarios": []
    }
    
    # Identify test scenarios
    analysis["test_scenarios"] = identify_test_scenarios(node, content)
    
    return analysis


def analyze_class(node: ast.ClassDef, content: str) -> Dict[str, Any]:
    """
    Analyze a class for testing requirements
    
    Args:
        node: Class AST node
        content: File content
        
    Returns:
        Class analysis
    """
    methods = [n for n in node.body if isinstance(n, ast.FunctionDef)]
    
    analysis = {
        "name": node.name,
        "line": node.lineno,
        "methods": len(methods),
        "public_methods": len([m for m in methods if not m.name.startswith('_')]),
        "init_method": any(m.name == '__init__' for m in methods),
        "base_classes": [base.id if isinstance(base, ast.Name) else str(base) for base in node.bases],
        "decorators": [d.id if isinstance(d, ast.Name) else str(d) for d in node.decorator_list],
        "test_scenarios": []
    }
    
    # Identify class test scenarios
    analysis["test_scenarios"] = identify_class_test_scenarios(node, content)
    
    return analysis


def analyze_import(node: ast.AST) -> Dict[str, Any]:
    """
    Analyze imports for testing setup
    
    Args:
        node: Import AST node
        
    Returns:
        Import analysis
    """
    if isinstance(node, ast.Import):
        return {
            "type": "import",
            "modules": [alias.name for alias in node.names],
            "line": node.lineno
        }
    else:  # ast.ImportFrom
        return {
            "type": "from_import",
            "module": node.module,
            "names": [alias.name for alias in node.names],
            "line": node.lineno
        }


def calculate_complexity(node: ast.FunctionDef) -> int:
    """
    Calculate cyclomatic complexity of a function
    
    Args:
        node: Function AST node
        
    Returns:
        Complexity score
    """
    complexity = 1  # Base complexity
    
    for child in ast.walk(node):
        if isinstance(child, (ast.If, ast.While, ast.For, ast.Try)):
            complexity += 1
        elif isinstance(child, ast.ExceptHandler):
            complexity += 1
        elif isinstance(child, ast.BoolOp):
            complexity += len(child.values) - 1
    
    return complexity


def identify_test_scenarios(node: ast.FunctionDef, content: str) -> List[Dict[str, Any]]:
    """
    Identify test scenarios for a function
    
    Args:
        node: Function AST node
        content: File content
        
    Returns:
        List of test scenarios
    """
    scenarios = []
    
    # Happy path scenario
    scenarios.append({
        "type": "happy_path",
        "description": f"Test {node.name} with valid inputs",
        "priority": "high"
    })
    
    # Edge cases based on parameters
    if node.args.args:
        scenarios.append({
            "type": "edge_case",
            "description": f"Test {node.name} with edge case inputs",
            "priority": "medium"
        })
    
    # Error scenarios
    for child in ast.walk(node):
        if isinstance(child, ast.Raise):
            scenarios.append({
                "type": "error_case",
                "description": f"Test {node.name} error handling",
                "priority": "medium"
            })
            break
    
    # Type validation scenarios
    if node.args.args:
        scenarios.append({
            "type": "type_validation",
            "description": f"Test {node.name} with invalid types",
            "priority": "low"
        })
    
    return scenarios


def identify_class_test_scenarios(node: ast.ClassDef, content: str) -> List[Dict[str, Any]]:
    """
    Identify test scenarios for a class
    
    Args:
        node: Class AST node
        content: File content
        
    Returns:
        List of test scenarios
    """
    scenarios = []
    
    # Initialization scenario
    scenarios.append({
        "type": "initialization",
        "description": f"Test {node.name} instantiation",
        "priority": "high"
    })
    
    # Method testing scenarios
    methods = [n for n in node.body if isinstance(n, ast.FunctionDef)]
    if methods:
        scenarios.append({
            "type": "method_testing",
            "description": f"Test {node.name} methods",
            "priority": "high"
        })
    
    # State testing scenarios
    scenarios.append({
        "type": "state_testing",
        "description": f"Test {node.name} state management",
        "priority": "medium"
    })
    
    return scenarios


def identify_testing_patterns(tree: ast.AST, content: str) -> List[str]:
    """
    Identify patterns that affect testing strategy
    
    Args:
        tree: AST tree
        content: File content
        
    Returns:
        List of identified patterns
    """
    patterns = []
    
    # Check for common patterns
    has_async = any(isinstance(node, ast.AsyncFunctionDef) for node in ast.walk(tree))
    if has_async:
        patterns.append("async_code")
    
    has_classes = any(isinstance(node, ast.ClassDef) for node in ast.walk(tree))
    if has_classes:
        patterns.append("object_oriented")
    
    has_decorators = any(
        node.decorator_list for node in ast.walk(tree) 
        if isinstance(node, (ast.FunctionDef, ast.ClassDef))
    )
    if has_decorators:
        patterns.append("decorator_usage")
    
    # Storm Logger specific patterns
    if "storm_logger" in content.lower():
        patterns.append("storm_logger_integration")
    
    if "logging" in content.lower():
        patterns.append("logging_code")
    
    return patterns


def generate_unit_tests(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate unit test cases
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of test cases
    """
    test_cases = []
    
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            test_case = {
                "function": node.name,
                "test_name": f"test_{node.name}",
                "type": "unit",
                "priority": "high" if not node.name.startswith('_') else "medium",
                "test_code": generate_function_test_code(node, framework),
                "description": f"Unit test for {node.name} function"
            }
            test_cases.append(test_case)
    
    return test_cases


def generate_integration_tests(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate integration test cases
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of test cases
    """
    test_cases = []
    
    # Look for classes that might need integration testing
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef):
            test_case = {
                "class": node.name,
                "test_name": f"test_{node.name.lower()}_integration",
                "type": "integration",
                "priority": "medium",
                "test_code": generate_class_integration_test_code(node, framework),
                "description": f"Integration test for {node.name} class"
            }
            test_cases.append(test_case)
    
    return test_cases


def generate_functional_tests(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate functional test cases
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of test cases
    """
    test_cases = []
    
    # Generate end-to-end functional tests
    test_case = {
        "function": "end_to_end_workflow",
        "test_name": "test_complete_workflow",
        "type": "functional",
        "priority": "high",
        "test_code": generate_functional_test_code(tree, framework),
        "description": "Functional test for complete workflow"
    }
    test_cases.append(test_case)
    
    return test_cases


def generate_performance_tests(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate performance test cases
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of test cases
    """
    test_cases = []
    
    # Look for functions that might need performance testing
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            complexity = calculate_complexity(node)
            if complexity > 5:  # High complexity functions
                test_case = {
                    "function": node.name,
                    "test_name": f"test_{node.name}_performance",
                    "type": "performance",
                    "priority": "medium",
                    "test_code": generate_performance_test_code(node, framework),
                    "description": f"Performance test for {node.name} function"
                }
                test_cases.append(test_case)
    
    return test_cases


def generate_comprehensive_tests(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate comprehensive test suite
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of test cases
    """
    test_cases = []
    
    # Combine all test types
    test_cases.extend(generate_unit_tests(tree, content, framework))
    test_cases.extend(generate_integration_tests(tree, content, framework))
    test_cases.extend(generate_functional_tests(tree, content, framework))
    test_cases.extend(generate_performance_tests(tree, content, framework))
    
    return test_cases


def generate_function_test_code(node: ast.FunctionDef, framework: str) -> str:
    """
    Generate test code for a function
    
    Args:
        node: Function AST node
        framework: Test framework
        
    Returns:
        Test code string
    """
    if framework == "pytest":
        return f"""
def test_{node.name}():
    \"\"\"Test {node.name} function\"\"\"
    # Arrange
    # TODO: Set up test data
    
    # Act
    # TODO: Call the function
    # result = {node.name}()
    
    # Assert
    # TODO: Verify the result
    # assert result == expected
    
    # Storm Logger integration
    # TODO: Verify logging behavior if applicable
    pass
"""
    elif framework == "unittest":
        return f"""
def test_{node.name}(self):
    \"\"\"Test {node.name} function\"\"\"
    # Arrange
    # TODO: Set up test data
    
    # Act
    # TODO: Call the function
    # result = {node.name}()
    
    # Assert
    # TODO: Verify the result
    # self.assertEqual(result, expected)
    
    # Storm Logger integration
    # TODO: Verify logging behavior if applicable
    pass
"""
    else:
        return f"# TODO: Implement test for {node.name}"


def generate_class_integration_test_code(node: ast.ClassDef, framework: str) -> str:
    """
    Generate integration test code for a class
    
    Args:
        node: Class AST node
        framework: Test framework
        
    Returns:
        Test code string
    """
    if framework == "pytest":
        return f"""
def test_{node.name.lower()}_integration():
    \"\"\"Integration test for {node.name} class\"\"\"
    # Arrange
    instance = {node.name}()
    
    # Act & Assert
    # TODO: Test class integration with other components
    
    # Storm Logger integration
    # TODO: Verify logging integration
    pass
"""
    else:
        return f"# TODO: Implement integration test for {node.name}"


def generate_functional_test_code(tree: ast.AST, framework: str) -> str:
    """
    Generate functional test code
    
    Args:
        tree: AST tree
        framework: Test framework
        
    Returns:
        Test code string
    """
    if framework == "pytest":
        return """
def test_complete_workflow():
    \"\"\"Functional test for complete workflow\"\"\"
    # Arrange
    # TODO: Set up complete test environment
    
    # Act
    # TODO: Execute complete workflow
    
    # Assert
    # TODO: Verify end-to-end functionality
    
    # Storm Logger integration
    # TODO: Verify complete logging workflow
    pass
"""
    else:
        return "# TODO: Implement functional test"


def generate_performance_test_code(node: ast.FunctionDef, framework: str) -> str:
    """
    Generate performance test code for a function
    
    Args:
        node: Function AST node
        framework: Test framework
        
    Returns:
        Test code string
    """
    if framework == "pytest":
        return f"""
def test_{node.name}_performance():
    \"\"\"Performance test for {node.name} function\"\"\"
    import time
    
    # Arrange
    # TODO: Set up performance test data
    
    # Act
    start_time = time.time()
    # TODO: Call the function multiple times
    # for _ in range(1000):
    #     {node.name}()
    end_time = time.time()
    
    # Assert
    execution_time = end_time - start_time
    # TODO: Define acceptable performance threshold
    # assert execution_time < 1.0  # 1 second threshold
    
    # Storm Logger performance tracking
    # TODO: Log performance metrics
    pass
"""
    else:
        return f"# TODO: Implement performance test for {node.name}"


def generate_test_fixtures(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate test fixtures
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of fixtures
    """
    fixtures = []
    
    # Generate common fixtures
    if framework == "pytest":
        # Storm Logger fixture
        fixtures.append({
            "name": "storm_logger_fixture",
            "type": "pytest.fixture",
            "code": """
@pytest.fixture
def storm_logger():
    \"\"\"Storm Logger fixture for testing\"\"\"
    from storm_logger import StormLogger
    logger = StormLogger()
    yield logger
    # Cleanup if needed
""",
            "description": "Storm Logger instance for testing"
        })
        
        # Mock fixture
        fixtures.append({
            "name": "mock_data_fixture",
            "type": "pytest.fixture",
            "code": """
@pytest.fixture
def mock_data():
    \"\"\"Mock data fixture for testing\"\"\"
    return {
        'test_data': 'test_value',
        'sample_list': [1, 2, 3],
        'sample_dict': {'key': 'value'}
    }
""",
            "description": "Mock data for testing"
        })
    
    return fixtures


def generate_test_mocks(tree: ast.AST, content: str, framework: str) -> List[Dict[str, Any]]:
    """
    Generate test mocks
    
    Args:
        tree: AST tree
        content: File content
        framework: Test framework
        
    Returns:
        List of mocks
    """
    mocks = []
    
    # Look for external dependencies to mock
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom):
            if node.module and not node.module.startswith('.'):
                mocks.append({
                    "module": node.module,
                    "type": "external_dependency",
                    "mock_code": f"""
@pytest.fixture
def mock_{node.module.replace('.', '_')}():
    \"\"\"Mock {node.module} for testing\"\"\"
    with patch('{node.module}') as mock:
        yield mock
""",
                    "description": f"Mock for {node.module} module"
                })
    
    return mocks


def generate_complete_test_file(
    source_path: Path, 
    test_cases: List[Dict[str, Any]], 
    fixtures: List[Dict[str, Any]], 
    mocks: List[Dict[str, Any]], 
    framework: str
) -> str:
    """
    Generate complete test file content
    
    Args:
        source_path: Path to source file
        test_cases: List of test cases
        fixtures: List of fixtures
        mocks: List of mocks
        framework: Test framework
        
    Returns:
        Complete test file content
    """
    module_name = source_path.stem
    
    if framework == "pytest":
        content = f'''"""
Test file for {module_name}
Generated by Storm Logger Python Test Generator
"""

import pytest
from unittest.mock import Mock, patch
from storm_logger import StormLogger

# Import the module being tested
from {module_name} import *


# Fixtures
'''
        
        # Add fixtures
        for fixture in fixtures:
            content += fixture["code"] + "\n\n"
        
        # Add mocks
        for mock in mocks:
            content += mock["mock_code"] + "\n\n"
        
        # Add test cases
        content += "# Test Cases\n"
        for test_case in test_cases:
            content += test_case["test_code"] + "\n\n"
        
        # Add Storm Logger integration tests
        content += '''
# Storm Logger Integration Tests

def test_storm_logger_integration(storm_logger_fixture):
    """Test Storm Logger integration"""
    logger = storm_logger_fixture
    
    # Test basic logging
    logger.info("Test message")
    
    # Test business metrics
    logger.track_business_event("test_event", {"key": "value"})
    
    # Assert logging behavior
    # TODO: Add specific assertions
    pass


def test_storm_logger_console_output(storm_logger_fixture):
    """Test Storm Logger console output"""
    logger = storm_logger_fixture
    
    # Test console output
    logger.log_business_success("Test success message")
    
    # TODO: Add console output assertions
    pass
'''
    
    else:
        content = f"# TODO: Implement test file for {module_name} using {framework}"
    
    return content


def analyze_coverage_potential(
    tree: ast.AST, 
    test_cases: List[Dict[str, Any]], 
    target: int
) -> Dict[str, Any]:
    """
    Analyze potential test coverage
    
    Args:
        tree: AST tree
        test_cases: List of test cases
        target: Target coverage percentage
        
    Returns:
        Coverage analysis
    """
    # Count total testable elements
    functions = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
    classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
    
    total_elements = len(functions) + len(classes)
    covered_elements = len(test_cases)
    
    current_coverage = (covered_elements / total_elements * 100) if total_elements > 0 else 0
    
    return {
        "total_elements": total_elements,
        "covered_elements": covered_elements,
        "current_coverage": round(current_coverage, 2),
        "target_coverage": target,
        "meets_target": current_coverage >= target,
        "missing_tests": max(0, int(total_elements * target / 100) - covered_elements),
        "recommendations": generate_coverage_recommendations(current_coverage, target)
    }


def generate_coverage_recommendations(current: float, target: int) -> List[str]:
    """
    Generate coverage improvement recommendations
    
    Args:
        current: Current coverage percentage
        target: Target coverage percentage
        
    Returns:
        List of recommendations
    """
    recommendations = []
    
    if current < target:
        gap = target - current
        recommendations.append(f"Increase coverage by {gap:.1f}% to meet target")
        
        if gap > 20:
            recommendations.append("Focus on unit tests for core functions")
            recommendations.append("Add integration tests for class interactions")
        elif gap > 10:
            recommendations.append("Add edge case tests")
            recommendations.append("Include error handling tests")
        else:
            recommendations.append("Add comprehensive assertion tests")
    
    recommendations.append("Consider property-based testing for complex functions")
    recommendations.append("Add performance benchmarks for critical paths")
    
    return recommendations


def generate_storm_logger_integration(
    tree: ast.AST, 
    content: str, 
    test_type: str
) -> Dict[str, Any]:
    """
    Generate Storm Logger specific integration
    
    Args:
        tree: AST tree
        content: File content
        test_type: Type of test
        
    Returns:
        Storm Logger integration details
    """
    integration = {
        "test_type": test_type,
        "storm_features": [],
        "integration_tests": [],
        "business_metrics": [],
        "console_tests": []
    }
    
    # Check for Storm Logger usage
    if "storm_logger" in content.lower():
        integration["storm_features"].append("logging_integration")
        integration["integration_tests"].append({
            "name": "test_storm_logger_integration",
            "description": "Test Storm Logger integration"
        })
    
    # Check for business metrics
    if "business" in content.lower() or "metrics" in content.lower():
        integration["storm_features"].append("business_metrics")
        integration["business_metrics"].append({
            "name": "test_business_metrics_collection",
            "description": "Test business metrics collection"
        })
    
    # Check for console output
    if "console" in content.lower() or "print" in content.lower():
        integration["storm_features"].append("console_output")
        integration["console_tests"].append({
            "name": "test_console_output",
            "description": "Test console output formatting"
        })
    
    return integration