"""N+1 query detection - pure Python, no C extensions.

This module provides heuristic-based N+1 query detection by analyzing SQL patterns.
All functions are pure (no side effects) and work with simple dataclasses.
"""

import re
from dataclasses import dataclass
from typing import List, Dict
from collections import defaultdict

import sys
import os

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from core.metrics import QueryInfo


@dataclass
class NPlusOneResult:
    """Result of N+1 query detection analysis."""

    detected: bool
    severity: str  # "NONE", "MILD", "MODERATE", "HIGH", "SEVERE"
    pattern_count: int  # Number of repeated patterns found
    total_queries: int
    suggestions: List[str]
    patterns: Dict[str, int]  # Pattern -> count mapping


def _normalize_query(sql: str) -> str:
    """Normalize SQL query to detect patterns.

    Removes values and parameters to identify structural patterns:
    - WHERE id = 123 becomes WHERE id = ?
    - WHERE name = 'foo' becomes WHERE name = ?

    Args:
        sql: Raw SQL query string

    Returns:
        Normalized query pattern
    """
    # Remove numbers
    pattern = re.sub(r"\b\d+\b", "?", sql)

    # Remove quoted strings
    pattern = re.sub(r"'[^']*'", "?", pattern)
    pattern = re.sub(r'"[^"]*"', "?", pattern)

    # Normalize whitespace
    pattern = " ".join(pattern.split())

    return pattern.upper()


def _group_by_pattern(queries: List[QueryInfo]) -> Dict[str, List[QueryInfo]]:
    """Group queries by normalized pattern.

    Args:
        queries: List of QueryInfo objects

    Returns:
        Dictionary mapping patterns to lists of matching queries
    """
    patterns: Dict[str, List[QueryInfo]] = defaultdict(list)

    for query in queries:
        pattern = _normalize_query(query.sql)
        patterns[pattern].append(query)

    return dict(patterns)


def _get_fix_suggestion(pattern: str, count: int) -> str:
    """Get optimization suggestion based on query pattern.

    Args:
        pattern: Normalized SQL pattern
        count: Number of times pattern occurs

    Returns:
        Human-readable suggestion for fixing the N+1 issue
    """
    pattern_upper = pattern.upper()

    # Check for common N+1 patterns
    if "SELECT" in pattern_upper and "WHERE" in pattern_upper:
        if "INNER JOIN" not in pattern_upper and "LEFT JOIN" not in pattern_upper:
            # Likely a foreign key lookup
            return (
                f"Use select_related() to fetch related objects in a single query "
                f"(currently making {count} separate queries)"
            )

    if "SELECT" in pattern_upper and "IN" in pattern_upper:
        # Might be fixable with prefetch_related
        return (
            f"Use prefetch_related() for reverse foreign keys or many-to-many "
            f"(currently making {count} separate queries)"
        )

    # Generic suggestion
    return (
        f"Optimize repeated query pattern ({count} occurrences) using "
        f"select_related(), prefetch_related(), or query optimization"
    )


def detect_n_plus_one(queries: List[QueryInfo], min_pattern_count: int = 10) -> NPlusOneResult:
    """Detect N+1 query patterns in a list of queries.

    An N+1 pattern is detected when:
    1. The same query pattern is repeated many times (>= min_pattern_count)
    2. The queries are structurally identical except for parameter values

    Args:
        queries: List of QueryInfo objects to analyze
        min_pattern_count: Minimum repetitions to flag as N+1 (default: 10)

    Returns:
        NPlusOneResult with detection results and suggestions

    Example:
        >>> queries = [QueryInfo("SELECT * FROM user WHERE id = 1", 0.001), ...]
        >>> result = detect_n_plus_one(queries)
        >>> if result.detected:
        ...     print(f"N+1 detected: {result.severity}")
        ...     for suggestion in result.suggestions:
        ...         print(f"  - {suggestion}")
    """
    if not queries:
        return NPlusOneResult(
            detected=False,
            severity="NONE",
            pattern_count=0,
            total_queries=0,
            suggestions=[],
            patterns={},
        )

    # Group queries by pattern
    pattern_groups = _group_by_pattern(queries)

    # Find patterns that repeat many times
    suspicious_patterns: Dict[str, int] = {}
    suggestions: List[str] = []

    for pattern, query_list in pattern_groups.items():
        count = len(query_list)

        if count >= min_pattern_count:
            suspicious_patterns[pattern[:100]] = count  # Truncate pattern for display
            suggestions.append(_get_fix_suggestion(pattern, count))

    # Determine severity based on worst pattern
    detected = len(suspicious_patterns) > 0
    max_count = max(suspicious_patterns.values()) if suspicious_patterns else 0

    if max_count == 0:
        severity = "NONE"
    elif max_count < 15:
        severity = "MILD"
    elif max_count < 25:
        severity = "MODERATE"
    elif max_count < 40:
        severity = "HIGH"
    else:
        severity = "SEVERE"

    return NPlusOneResult(
        detected=detected,
        severity=severity,
        pattern_count=len(suspicious_patterns),
        total_queries=len(queries),
        suggestions=suggestions,
        patterns=suspicious_patterns,
    )
