"""
Helper Functions for MCP Servers - S-Tier Best Practices

This module provides reusable helpers that demonstrate:
- DRY principle (Don't Repeat Yourself)
- Single responsibility functions
- Type safety throughout
- Consistent error handling patterns
"""

from typing import List, Dict, Any, Optional, TypeVar, Generic
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass
import json

from .models import MCPResponse, Status
from .constants import (
    ErrorMessage, MAX_EXAMPLES_PER_TOOL, MAX_NEXT_STEPS,
    MAX_RELATED_TOOLS, TestMarker
)
from .type_hints import ErrorContext, NextStep, ToolExample


# ============================================================================
# Generic Response Builders
# ============================================================================

T = TypeVar('T')


@dataclass
class ResponseBuilder(Generic[T]):
    """
    Generic response builder for consistent MCP responses.
    
    Example:
        >>> builder = ResponseBuilder(MyResponseClass)
        >>> response = (builder
        ...     .with_status(Status.SUCCESS)
        ...     .with_message("Operation completed")
        ...     .with_data({"result": 42})
        ...     .build())
    """
    response_class: type[T]
    _status: Status = Status.SUCCESS
    _message: str = ""
    _data: Any = None
    _next_steps: List[str] = None
    _related_tools: List[str] = None
    _examples: List[str] = None
    
    def with_status(self, status: Status) -> 'ResponseBuilder[T]':
        """Set response status."""
        self._status = status
        return self
    
    def with_message(self, message: str) -> 'ResponseBuilder[T]':
        """Set response message."""
        self._message = message
        return self
    
    def with_data(self, data: Any) -> 'ResponseBuilder[T]':
        """Set response data."""
        self._data = data
        return self
    
    def with_next_steps(self, steps: List[str]) -> 'ResponseBuilder[T]':
        """Set next steps (limited to MAX_NEXT_STEPS)."""
        self._next_steps = steps[:MAX_NEXT_STEPS]
        return self
    
    def with_related_tools(self, tools: List[str]) -> 'ResponseBuilder[T]':
        """Set related tools (limited to MAX_RELATED_TOOLS)."""
        self._related_tools = tools[:MAX_RELATED_TOOLS]
        return self
    
    def with_examples(self, examples: List[str]) -> 'ResponseBuilder[T]':
        """Set examples (limited to MAX_EXAMPLES_PER_TOOL)."""
        self._examples = examples[:MAX_EXAMPLES_PER_TOOL]
        return self
    
    def build(self) -> T:
        """Build the response object."""
        return self.response_class(
            status=self._status,
            message=self._message,
            data=self._data or {},
            next_steps=self._next_steps or [],
            related_tools=self._related_tools or [],
            examples=self._examples or []
        )


# ============================================================================
# Error Handling Helpers
# ============================================================================

def create_error_response(
    error: Exception,
    error_context: Optional[Dict[str, Any]] = None,
    suggestions: Optional[List[str]] = None
) -> MCPResponse:
    """
    Create a consistent error response from an exception.
    
    Args:
        error: The exception that occurred
        error_context: Additional context about the error
        suggestions: Specific suggestions for recovery
        
    Returns:
        MCPResponse with error details and helpful suggestions
        
    Example:
        >>> try:
        ...     risky_operation()
        >>> except FileNotFoundError as e:
        ...     return create_error_response(
        ...         e,
        ...         error_context={"path": "/missing/file"},
        ...         suggestions=["Check if file exists", "Verify path"]
        ...     )
    """
    error_type = type(error).__name__
    
    # Generate smart suggestions if not provided
    if suggestions is None:
        suggestions = generate_error_suggestions(error_type, str(error))
    
    error_data: ErrorContext = {
        "error_type": error_type,
        "error_message": str(error),
        "stack_trace": None,  # Can be enhanced with traceback
        "context": error_context or {},
        "suggestions": suggestions,
        "related_documentation": get_error_documentation(error_type)
    }
    
    return MCPResponse(
        status=Status.ERROR,
        message=f"{error_type}: {str(error)}",
        data=error_data,
        next_steps=suggestions[:MAX_NEXT_STEPS],
        related_tools=["health_check", "analyze_project_structure"],
        examples=get_error_recovery_examples(error_type)
    )


def generate_error_suggestions(error_type: str, error_message: str) -> List[str]:
    """
    Generate intelligent suggestions based on error type and message.
    
    This demonstrates pattern matching without regex hell!
    """
    suggestions = []
    
    # File-related errors
    if "FileNotFound" in error_type or "No such file" in error_message:
        suggestions.extend([
            "Verify the file path is correct",
            "Check if you're in the right directory",
            "Use absolute paths for clarity",
            "Run 'ls' or 'find' to locate the file"
        ])
    
    # Permission errors
    elif "Permission" in error_type or "access denied" in error_message.lower():
        suggestions.extend([
            "Check file and directory permissions",
            "Ensure you have read/write access",
            "Try running with appropriate permissions",
            "Verify ownership of files"
        ])
    
    # Import/Module errors
    elif "ModuleNotFound" in error_type or "ImportError" in error_type:
        suggestions.extend([
            "Install missing dependencies: pip install <package>",
            "Check if virtual environment is activated",
            "Verify PYTHONPATH is set correctly",
            "Run 'pip list' to see installed packages"
        ])
    
    # Configuration errors
    elif "Configuration" in error_type or "config" in error_message.lower():
        suggestions.extend([
            "Review configuration file format",
            "Check for missing required fields",
            "Validate configuration values",
            "Use 'validate_config' tool if available"
        ])
    
    # Generic suggestions
    else:
        suggestions.extend([
            "Review the error message for details",
            "Check recent changes that might have caused this",
            "Run 'health_check' for system diagnostics",
            "Consult documentation or examples"
        ])
    
    return suggestions


def get_error_documentation(error_type: str) -> List[str]:
    """Get relevant documentation links for error types."""
    docs = {
        "FileNotFoundError": [
            "https://docs.python.org/3/library/exceptions.html#FileNotFoundError",
            "https://docs.storm-logger.dev/troubleshooting#file-errors"
        ],
        "PermissionError": [
            "https://docs.python.org/3/library/exceptions.html#PermissionError",
            "https://docs.storm-logger.dev/troubleshooting#permissions"
        ],
        "ModuleNotFoundError": [
            "https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError",
            "https://packaging.python.org/tutorials/installing-packages/"
        ]
    }
    return docs.get(error_type, ["https://docs.storm-logger.dev/troubleshooting"])


def get_error_recovery_examples(error_type: str) -> List[str]:
    """Get recovery command examples for common errors."""
    examples = {
        "FileNotFoundError": [
            "ls -la /path/to/check/",
            "find . -name 'filename.py'",
            "pwd  # Check current directory"
        ],
        "PermissionError": [
            "ls -la file.py  # Check permissions",
            "chmod 644 file.py  # Fix permissions",
            "sudo chown $USER:$USER file.py"
        ],
        "ModuleNotFoundError": [
            "pip install pytest pytest-cov",
            "pip install -r requirements.txt",
            "python -m venv venv && source venv/bin/activate"
        ]
    }
    return examples.get(error_type, ["python --version", "pip --version"])[:MAX_EXAMPLES_PER_TOOL]


# ============================================================================
# Path and File Helpers
# ============================================================================

def validate_project_path(path: Optional[str] = None, default: Path = None) -> Path:
    """
    Validate and normalize a project path.
    
    Args:
        path: String path to validate
        default: Default path if none provided
        
    Returns:
        Validated Path object
        
    Raises:
        ValueError: If path doesn't exist or isn't a directory
        
    Example:
        >>> path = validate_project_path("/home/user/project")
        >>> # Returns Path object if valid
        >>> 
        >>> path = validate_project_path(None, Path.cwd())
        >>> # Uses current directory as default
    """
    if path is None:
        project_path = default or Path.cwd()
    else:
        project_path = Path(path)
    
    if not project_path.exists():
        raise ValueError(ErrorMessage.PROJECT_NOT_FOUND.value.format(path=project_path))
    
    if not project_path.is_dir():
        raise ValueError(f"Path is not a directory: {project_path}")
    
    return project_path.resolve()


def find_python_files(
    root_path: Path,
    pattern: str = "*.py",
    exclude_dirs: Optional[List[str]] = None
) -> List[Path]:
    """
    Find Python files in a directory tree.
    
    Args:
        root_path: Root directory to search
        pattern: Glob pattern for files
        exclude_dirs: Directory names to exclude
        
    Returns:
        List of Path objects for found files
        
    Example:
        >>> files = find_python_files(Path("/project"))
        >>> # Returns all .py files
        >>> 
        >>> test_files = find_python_files(
        ...     Path("/project"),
        ...     pattern="test_*.py"
        ... )
    """
    if exclude_dirs is None:
        exclude_dirs = ["__pycache__", ".git", "venv", ".venv"]
    
    files = []
    for path in root_path.rglob(pattern):
        # Check if any parent directory should be excluded
        if any(excluded in path.parts for excluded in exclude_dirs):
            continue
        files.append(path)
    
    return sorted(files)


# ============================================================================
# Test Generation Helpers
# ============================================================================

def generate_test_imports(
    module_path: str,
    test_framework: str = "pytest",
    include_mocks: bool = True
) -> List[str]:
    """
    Generate standard test imports.
    
    Args:
        module_path: Path to module being tested
        test_framework: Testing framework to use
        include_mocks: Whether to include mocking imports
        
    Returns:
        List of import statements
        
    Example:
        >>> imports = generate_test_imports("storm_logger.utils.sanitizer")
        >>> # Returns:
        >>> # ["import pytest",
        >>> #  "from unittest.mock import Mock, patch",
        >>> #  "from storm_logger.utils.sanitizer import *"]
    """
    imports = []
    
    if test_framework == "pytest":
        imports.append("import pytest")
    elif test_framework == "unittest":
        imports.append("import unittest")
    
    if include_mocks:
        imports.append("from unittest.mock import Mock, patch, MagicMock")
    
    imports.extend([
        f"from {module_path} import *",
        "from pathlib import Path",
        "from typing import Any, Dict, List, Optional"
    ])
    
    return imports


def generate_pytest_markers(
    test_type: TestMarker,
    additional_markers: Optional[List[str]] = None
) -> List[str]:
    """
    Generate pytest markers for test decoration.
    
    Args:
        test_type: Primary test type
        additional_markers: Additional markers to include
        
    Returns:
        List of pytest marker decorators
        
    Example:
        >>> markers = generate_pytest_markers(TestMarker.UNIT, ["slow"])
        >>> # Returns: ["@pytest.mark.unit", "@pytest.mark.slow"]
    """
    markers = [f"@pytest.mark.{test_type.value}"]
    
    if additional_markers:
        for marker in additional_markers:
            markers.append(f"@pytest.mark.{marker}")
    
    return markers


# ============================================================================
# Business Intelligence Helpers
# ============================================================================

def calculate_business_impact(
    coverage_gap: float,
    module_criticality: float,
    usage_frequency: float
) -> float:
    """
    Calculate business impact score for coverage gaps.
    
    Args:
        coverage_gap: Percentage of uncovered code (0-100)
        module_criticality: Criticality score (0-1)
        usage_frequency: Usage frequency score (0-1)
        
    Returns:
        Business impact score (0-100)
        
    Example:
        >>> impact = calculate_business_impact(
        ...     coverage_gap=15.0,      # 15% uncovered
        ...     module_criticality=0.9, # Very critical
        ...     usage_frequency=0.8     # Frequently used
        ... )
        >>> # Returns: 72.0 (high impact)
    """
    # Weighted calculation
    gap_weight = 0.4
    criticality_weight = 0.4
    frequency_weight = 0.2
    
    impact = (
        (coverage_gap * gap_weight) +
        (module_criticality * 100 * criticality_weight) +
        (usage_frequency * 100 * frequency_weight)
    )
    
    return min(100.0, max(0.0, impact))


def prioritize_by_business_value(
    items: List[Dict[str, Any]],
    value_key: str = "business_value",
    limit: Optional[int] = None
) -> List[Dict[str, Any]]:
    """
    Sort items by business value in descending order.
    
    Args:
        items: List of items to prioritize
        value_key: Key containing business value
        limit: Maximum items to return
        
    Returns:
        Sorted list of items
        
    Example:
        >>> modules = [
        ...     {"name": "logger", "business_value": 80},
        ...     {"name": "utils", "business_value": 95}
        ... ]
        >>> prioritized = prioritize_by_business_value(modules)
        >>> # Returns utils first (higher value)
    """
    sorted_items = sorted(
        items,
        key=lambda x: x.get(value_key, 0),
        reverse=True
    )
    
    if limit:
        return sorted_items[:limit]
    return sorted_items


# ============================================================================
# Formatting Helpers
# ============================================================================

def format_percentage(value: float, precision: int = 1) -> str:
    """
    Format percentage with consistent precision.
    
    Example:
        >>> format_percentage(85.456)
        '85.5%'
        >>> format_percentage(100.0)
        '100.0%'
    """
    return f"{value:.{precision}f}%"


def format_time_estimate(seconds: float) -> str:
    """
    Format time estimates in human-readable form.
    
    Example:
        >>> format_time_estimate(45)
        '45s'
        >>> format_time_estimate(125)
        '2m 5s'
        >>> format_time_estimate(3700)
        '1h 1m'
    """
    if seconds < 60:
        return f"{int(seconds)}s"
    elif seconds < 3600:
        minutes = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{minutes}m {secs}s" if secs else f"{minutes}m"
    else:
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        return f"{hours}h {minutes}m" if minutes else f"{hours}h"


def format_file_size(bytes_size: int) -> str:
    """
    Format file size in human-readable form.
    
    Example:
        >>> format_file_size(1234)
        '1.2 KB'
        >>> format_file_size(1234567)
        '1.2 MB'
    """
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if bytes_size < 1024.0:
            return f"{bytes_size:.1f} {unit}"
        bytes_size /= 1024.0
    return f"{bytes_size:.1f} PB"


# ============================================================================
# JSON Helpers
# ============================================================================

def safe_json_loads(json_string: str, default: Any = None) -> Any:
    """
    Safely load JSON with fallback.
    
    Args:
        json_string: JSON string to parse
        default: Default value if parsing fails
        
    Returns:
        Parsed JSON or default value
        
    Example:
        >>> data = safe_json_loads('{"key": "value"}')
        >>> # Returns: {"key": "value"}
        >>> 
        >>> data = safe_json_loads('invalid json', {})
        >>> # Returns: {}
    """
    try:
        return json.loads(json_string)
    except (json.JSONDecodeError, TypeError):
        return default


def pretty_json(data: Any, indent: int = 2) -> str:
    """
    Convert data to pretty-printed JSON.
    
    Example:
        >>> print(pretty_json({"a": 1, "b": [2, 3]}))
        {
          "a": 1,
          "b": [
            2,
            3
          ]
        }
    """
    return json.dumps(data, indent=indent, default=str)


# ============================================================================
# Validation Helpers
# ============================================================================

def validate_percentage(value: float, param_name: str = "percentage") -> float:
    """
    Validate percentage is in valid range.
    
    Args:
        value: Value to validate
        param_name: Parameter name for error messages
        
    Returns:
        Validated value
        
    Raises:
        ValueError: If value is outside 0-100 range
        
    Example:
        >>> validate_percentage(85.5)
        85.5
        >>> validate_percentage(150.0)
        ValueError: percentage must be between 0 and 100
    """
    if not 0 <= value <= 100:
        raise ValueError(f"{param_name} must be between 0 and 100, got {value}")
    return value


def validate_positive_int(value: int, param_name: str = "value") -> int:
    """
    Validate integer is positive.
    
    Example:
        >>> validate_positive_int(5, "timeout")
        5
        >>> validate_positive_int(-1, "timeout")
        ValueError: timeout must be positive, got -1
    """
    if value <= 0:
        raise ValueError(f"{param_name} must be positive, got {value}")
    return value