"""
Change type detection - identifies what type of change was made to symbols.

Analyzes git diff to determine if symbols were added, removed, modified, etc.
"""

from typing import Dict, List, Tuple
import re


# Regex patterns for different change types
PATTERNS = {
    # Function/method definitions
    "function_added": r"^\+\s*(?:async\s+)?def\s+(\w+)\s*\(",
    "function_removed": r"^\-\s*(?:async\s+)?def\s+(\w+)\s*\(",
    "function_signature_changed": None,  # Computed separately
    
    # Class definitions
    "class_added": r"^\+\s*class\s+(\w+)\s*[\(\:]",
    "class_removed": r"^\-\s*class\s+(\w+)\s*[\(\:]",
    "class_modified": None,  # Computed separately
    
    # Variable/constant assignments
    "variable_added": r"^\+\s+(?:const|let|var)?\s*([a-zA-Z_]\w*)\s*=",
    "variable_removed": r"^\-\s+(?:const|let|var)?\s*([a-zA-Z_]\w*)\s*=",
    
    # Python specific variable assignments (module level)
    "py_variable_added": r"^\+\s+([A-Z_][A-Z0-9_]*)\s*=",
    "py_variable_removed": r"^\-\s+([A-Z_][A-Z0-9_]*)\s*=",
}


def detect_change_type(diff_content: str) -> Dict[str, str]:
    """
    Detect the type of change made to each symbol in a diff.
    
    Analyzes the git diff and classifies each symbol change as:
    - function added
    - function removed
    - function signature changed
    - class added
    - class removed
    - class modified
    - variable added
    - variable removed
    - variable modified
    
    Args:
        diff_content: Raw git diff content
        
    Returns:
        Dict mapping symbol names to change types
        
    Example:
        {
            "calculateDiscount": "function signature changed",
            "OrderStatus": "class modified",
            "MAX_RETRY": "variable modified"
        }
    """
    change_types = {}
    
    # Track which symbols were added and removed (for signature change detection)
    added_symbols = set()
    removed_symbols = set()
    
    # First pass: detect straightforward additions and removals
    for change_type, pattern in PATTERNS.items():
        if pattern is None:
            continue
        
        matches = re.findall(pattern, diff_content, re.MULTILINE)
        
        for symbol in matches:
            if symbol in change_types:
                # Already classified, skip
                continue
            
            if "added" in change_type:
                change_types[symbol] = change_type.replace("_added", " added")
                added_symbols.add(symbol)
            elif "removed" in change_type:
                change_types[symbol] = change_type.replace("_removed", " removed")
                removed_symbols.add(symbol)
    
    # Second pass: detect signature changes (both added and removed lines)
    signature_changes = _detect_signature_changes(diff_content, added_symbols, removed_symbols)
    change_types.update(signature_changes)
    
    # Third pass: detect modifications (lines changed within a block)
    modifications = _detect_modifications(diff_content, change_types)
    change_types.update(modifications)
    
    # Clean up change type names
    change_types = {
        symbol: _normalize_change_type(change_type)
        for symbol, change_type in change_types.items()
    }
    
    return change_types


def _detect_signature_changes(
    diff_content: str,
    added_symbols: set,
    removed_symbols: set
) -> Dict[str, str]:
    """
    Detect function/method signature changes.
    
    If a symbol appears in both added and removed lines, it's likely
    a signature change rather than a full addition/removal.
    
    Args:
        diff_content: Raw git diff
        added_symbols: Symbols that were added
        removed_symbols: Symbols that were removed
        
    Returns:
        Dict of signature changes
    """
    signature_changes = {}
    
    # Find symbols that appear in both added and removed (signature changed)
    for symbol in added_symbols & removed_symbols:
        # Verify it's actually a signature change by checking function patterns
        if _is_function_signature_change(diff_content, symbol):
            signature_changes[symbol] = "function signature changed"
        elif _is_class_modification(diff_content, symbol):
            signature_changes[symbol] = "class modified"
    
    return signature_changes


def _is_function_signature_change(diff_content: str, symbol: str) -> bool:
    """
    Check if a symbol represents a function signature change.
    
    Args:
        diff_content: Git diff content
        symbol: Symbol to check
        
    Returns:
        True if likely a function signature change
    """
    # Look for both removed and added function definitions with same name
    removed_pattern = rf"^\-\s*(?:async\s+)?def\s+{re.escape(symbol)}\s*\("
    added_pattern = rf"^\+\s*(?:async\s+)?def\s+{re.escape(symbol)}\s*\("
    
    has_removed = bool(re.search(removed_pattern, diff_content, re.MULTILINE))
    has_added = bool(re.search(added_pattern, diff_content, re.MULTILINE))
    
    return has_removed and has_added


def _is_class_modification(diff_content: str, symbol: str) -> bool:
    """
    Check if a symbol represents a class modification.
    
    Args:
        diff_content: Git diff content
        symbol: Symbol to check
        
    Returns:
        True if likely a class modification
    """
    # Look for both removed and added class definitions with same name
    removed_pattern = rf"^\-\s*class\s+{re.escape(symbol)}\s*[\(\:]"
    added_pattern = rf"^\+\s*class\s+{re.escape(symbol)}\s*[\(\:]"
    
    has_removed = bool(re.search(removed_pattern, diff_content, re.MULTILINE))
    has_added = bool(re.search(added_pattern, diff_content, re.MULTILINE))
    
    return has_removed and has_added


def _detect_modifications(
    diff_content: str,
    already_classified: Dict[str, str]
) -> Dict[str, str]:
    """
    Detect variable/constant modifications.
    
    Looks for lines that have both additions and removals in the same
    logical block (within ~10 lines of each other).
    
    Args:
        diff_content: Git diff content
        already_classified: Symbols already classified
        
    Returns:
        Dict of detected modifications
    """
    modifications = {}
    
    lines = diff_content.split("\n")
    
    # Find variable assignments that have both removed and added versions
    for i, line in enumerate(lines):
        # Look for removed assignments
        if line.startswith("-") and "=" in line:
            match = re.search(r"[\-\s]+([a-zA-Z_]\w*)\s*=", line)
            if match:
                symbol = match.group(1)
                if symbol in already_classified:
                    continue
                
                # Look ahead for an added version (within 5 lines)
                for j in range(i + 1, min(i + 6, len(lines))):
                    next_line = lines[j]
                    if next_line.startswith("+") and symbol in next_line and "=" in next_line:
                        modifications[symbol] = "variable modified"
                        break
    
    return modifications


def _normalize_change_type(change_type: str) -> str:
    """
    Normalize change type string to consistent format.
    
    Converts "function_added" to "function added", etc.
    
    Args:
        change_type: Raw change type string
        
    Returns:
        Normalized change type
    """
    # Remove underscores and ensure consistent formatting
    normalized = change_type.replace("_", " ").lower().strip()
    
    # Handle specific mappings
    mappings = {
        "function added": "function added",
        "function removed": "function removed",
        "function signature changed": "function signature changed",
        "class added": "class added",
        "class removed": "class removed",
        "class modified": "class modified",
        "variable added": "variable added",
        "variable removed": "variable removed",
        "variable modified": "variable modified",
        "enum added": "enum added",
        "enum removed": "enum removed",
        "enum modified": "enum value changed",
        "py variable added": "variable added",
        "py variable removed": "variable removed",
    }
    
    return mappings.get(normalized, normalized)


def enrich_symbol_with_change_type(
    symbols: List[str],
    change_types: Dict[str, str]
) -> Dict[str, Dict[str, str]]:
    """
    Enrich symbol list with change type information.
    
    Combines symbol detection with change type classification.
    
    Args:
        symbols: List of detected symbols
        change_types: Dict mapping symbols to change types
        
    Returns:
        Dict mapping symbols to metadata including change type
        
    Example:
        {
            "calculateDiscount": {
                "name": "calculateDiscount",
                "change": "function signature changed",
                "confidence": "high"
            }
        }
    """
    enriched = {}
    
    for symbol in symbols:
        change_type = change_types.get(symbol, "unknown")
        
        enriched[symbol] = {
            "name": symbol,
            "change": change_type,
            "confidence": _assess_confidence(change_type)
        }
    
    return enriched


def _assess_confidence(change_type: str) -> str:
    """
    Assess confidence level of change type detection.
    
    Args:
        change_type: Detected change type
        
    Returns:
        Confidence level: "high", "medium", or "low"
    """
    high_confidence_types = {
        "function added",
        "function removed",
        "class added",
        "class removed",
        "variable added",
        "variable removed",
    }
    
    medium_confidence_types = {
        "function signature changed",
        "class modified",
        "variable modified",
    }
    
    if change_type in high_confidence_types:
        return "high"
    elif change_type in medium_confidence_types:
        return "medium"
    else:
        return "low"


def format_change_summary(change_types: Dict[str, str]) -> str:
    """
    Format change types into a human-readable summary.
    
    Args:
        change_types: Dict mapping symbols to change types
        
    Returns:
        Formatted summary string
        
    Example:
        Functions: 2 added, 1 removed, 1 signature changed
        Classes: 1 added, 1 modified
        Variables: 3 modified
    """
    if not change_types:
        return "No changes detected"
    
    categories = {
        "function": [],
        "class": [],
        "variable": [],
        "enum": [],
    }
    
    # Categorize changes
    for symbol, change_type in change_types.items():
        for category in categories:
            if category in change_type:
                categories[category].append(change_type)
                break
    
    # Format summary
    lines = []
    for category, changes in categories.items():
        if changes:
            # Count by type
            type_counts = {}
            for change in changes:
                type_counts[change] = type_counts.get(change, 0) + 1
            
            # Format line
            parts = [f"{count} {change_type}" for change_type, count in type_counts.items()]
            lines.append(f"{category.capitalize()}s: {', '.join(parts)}")
    
    return "\n".join(lines) if lines else "No changes detected"
