"""
Performance monitoring utilities and context managers.
"""

import logging
import time
from contextlib import contextmanager
from functools import wraps
from typing import Any

from django.conf import settings
from django.db import connection
from django.utils import timezone

logger = logging.getLogger("performance")


@contextmanager
def monitor_performance(operation_name: str, **tags):
    """Context manager for monitoring operation performance"""
    start_time = time.time()
    initial_queries = len(connection.queries)

    # Create performance context
    performance_context = {
        "operation": operation_name,
        "start_time": start_time,
        "timestamp": timezone.now().isoformat(),
        **tags,
    }

    try:
        yield performance_context
    except Exception as e:
        performance_context["error"] = str(e)
        performance_context["status"] = "error"
        raise
    else:
        performance_context["status"] = "success"
    finally:
        end_time = time.time()
        duration = end_time - start_time
        total_queries = len(connection.queries) - initial_queries

        # Update performance context with final metrics
        performance_context.update(
            {
                "duration_seconds": duration,
                "duration_ms": round(duration * 1000, 2),
                "query_count": total_queries,
                "end_time": end_time,
            }
        )

        # Log performance data
        log_level = logging.WARNING if duration > 2.0 or total_queries > 10 else logging.INFO
        logger.log(
            log_level,
            f"Performance: {operation_name} completed in {duration:.3f}s with {
                total_queries
            } queries",
            extra=performance_context,
        )

        # Log slow operations with additional detail
        if duration > 2.0:
            logger.warning(
                f"Slow operation detected: {operation_name} took {duration:.3f}s",
                extra={
                    "slow_operation": True,
                    "threshold_exceeded": "duration",
                    **performance_context,
                },
            )

        if total_queries > 10:
            logger.warning(
                f"High query count: {operation_name} executed {total_queries} queries",
                extra={
                    "high_query_count": True,
                    "threshold_exceeded": "query_count",
                    **performance_context,
                },
            )


@contextmanager
def track_queries(operation_name: str, warn_threshold: int = 10):
    """Context manager to track database queries for specific operations"""
    if not settings.DEBUG:
        yield
        return

    initial_queries = len(connection.queries)
    start_time = time.time()

    try:
        yield
    finally:
        end_time = time.time()
        total_queries = len(connection.queries) - initial_queries
        execution_time = end_time - start_time

        query_details = []
        if hasattr(connection, "queries") and total_queries > 0:
            recent_queries = connection.queries[-total_queries:]
            query_details = [
                {
                    "sql": (query["sql"][:200] + "..." if len(query["sql"]) > 200 else query["sql"]),
                    "time": float(query["time"]),
                }
                for query in recent_queries
            ]

        performance_data = {
            "operation": operation_name,
            "query_count": total_queries,
            "execution_time": execution_time,
            "queries": query_details if settings.DEBUG else [],
        }

        if total_queries > warn_threshold or execution_time > 1.0:
            logger.warning(
                f"Performance concern in {operation_name}: " f"{total_queries} queries, {execution_time:.2f}s",
                extra=performance_data,
            )
        else:
            logger.debug(
                f"Query tracking for {operation_name}: " f"{total_queries} queries, {execution_time:.2f}s",
                extra=performance_data,
            )


class PerformanceProfiler:
    """Advanced performance profiling with detailed metrics"""

    def __init__(self, name: str):
        self.name = name
        self.start_time = None
        self.end_time = None
        self.checkpoints = []
        self.initial_queries = 0
        self.memory_usage = {}

    def start(self):
        """Start profiling"""
        self.start_time = time.time()
        self.initial_queries = len(connection.queries)

        # Track memory usage if psutil is available
        try:
            import psutil

            process = psutil.Process()
            self.memory_usage["start"] = process.memory_info().rss
        except ImportError:
            pass

        logger.debug(f"Started profiling: {self.name}")

    def checkpoint(self, name: str):
        """Add a checkpoint"""
        if self.start_time is None:
            logger.warning(f"Checkpoint '{name}' called before profiling started")
            return

        current_time = time.time()
        elapsed = current_time - self.start_time
        queries_since_start = len(connection.queries) - self.initial_queries

        checkpoint = {
            "name": name,
            "timestamp": current_time,
            "elapsed_seconds": elapsed,
            "queries_since_start": queries_since_start,
        }

        # Memory usage if available
        try:
            import psutil

            process = psutil.Process()
            checkpoint["memory_rss"] = process.memory_info().rss
        except ImportError:
            pass

        self.checkpoints.append(checkpoint)
        logger.debug(f"Checkpoint '{name}' at {elapsed:.3f}s")

    def stop(self):
        """Stop profiling and log results"""
        if self.start_time is None:
            logger.warning("Profiling stopped before it was started")
            return

        self.end_time = time.time()
        total_duration = self.end_time - self.start_time
        total_queries = len(connection.queries) - self.initial_queries

        # Final memory usage
        try:
            import psutil

            process = psutil.Process()
            self.memory_usage["end"] = process.memory_info().rss
        except ImportError:
            pass

        # Create detailed profiling report
        report = {
            "profiler_name": self.name,
            "total_duration": total_duration,
            "total_queries": total_queries,
            "checkpoints": self.checkpoints,
            "memory_usage": self.memory_usage,
            "queries_per_second": (total_queries / total_duration if total_duration > 0 else 0),
        }

        # Calculate checkpoint intervals
        if len(self.checkpoints) > 1:
            intervals = []
            for i in range(1, len(self.checkpoints)):
                prev = self.checkpoints[i - 1]
                curr = self.checkpoints[i]
                intervals.append(
                    {
                        "from": prev["name"],
                        "to": curr["name"],
                        "duration": curr["elapsed_seconds"] - prev["elapsed_seconds"],
                        "queries": curr["queries_since_start"] - prev["queries_since_start"],
                    }
                )
            report["checkpoint_intervals"] = intervals

        # Log the complete report
        log_level = logging.WARNING if total_duration > 1.0 else logging.INFO
        logger.log(
            log_level,
            f"Profiling complete: {self.name} took {total_duration:.3f}s with {
                total_queries
            } queries",
            extra=report,
        )

        return report


@contextmanager
def profile_operation(name: str):
    """Context manager for detailed operation profiling"""
    profiler = PerformanceProfiler(name)
    profiler.start()

    try:
        yield profiler
    finally:
        profiler.stop()


class DatabaseQueryAnalyzer:
    """Analyze database query patterns and performance"""

    @staticmethod
    def analyze_queries(queries: list[dict]) -> dict[str, Any]:
        """Analyze a list of queries for patterns and issues"""
        if not queries:
            return {}

        total_time = sum(float(q.get("time", 0)) for q in queries)
        query_count = len(queries)

        # Group queries by type
        query_types = {}
        for query in queries:
            sql = query.get("sql", "").strip().upper()
            query_type = sql.split()[0] if sql else "UNKNOWN"
            query_types[query_type] = query_types.get(query_type, 0) + 1

        # Find slow queries (top 10% by time)
        sorted_queries = sorted(queries, key=lambda q: float(q.get("time", 0)), reverse=True)
        slow_query_count = max(1, query_count // 10)
        slow_queries = sorted_queries[:slow_query_count]

        # Detect duplicate queries
        query_signatures = {}
        for query in queries:
            # Simplified signature - remove literals and normalize whitespace
            sql = query.get("sql", "")
            signature = " ".join(sql.split())  # Normalize whitespace
            query_signatures[signature] = query_signatures.get(signature, 0) + 1

        duplicates = {sig: count for sig, count in query_signatures.items() if count > 1}

        analysis = {
            "total_queries": query_count,
            "total_time": total_time,
            "average_time": total_time / query_count if query_count > 0 else 0,
            "query_types": query_types,
            "slow_queries": [
                {
                    "sql": (q.get("sql", "")[:200] + "..." if len(q.get("sql", "")) > 200 else q.get("sql", "")),
                    "time": float(q.get("time", 0)),
                }
                for q in slow_queries
            ],
            "duplicate_query_count": len(duplicates),
            "duplicate_queries": (duplicates if len(duplicates) <= 10 else dict(list(duplicates.items())[:10])),
        }

        return analysis

    @classmethod
    def analyze_current_queries(cls) -> dict[str, Any]:
        """Analyze the current request's queries"""
        if hasattr(connection, "queries"):
            return cls.analyze_queries(connection.queries)
        return {}


# Performance monitoring decorators
def monitor_function_performance(operation_name: str | None = None):
    """Decorator to monitor function performance"""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            name = operation_name or f"{func.__module__}.{func.__name__}"
            with monitor_performance(name, function=func.__name__, module=func.__module__):
                return func(*args, **kwargs)

        return wrapper

    return decorator


def track_database_queries(warn_threshold: int = 10):
    """Decorator to track database queries for a function"""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            operation_name = f"{func.__module__}.{func.__name__}"
            with track_queries(operation_name, warn_threshold):
                return func(*args, **kwargs)

        return wrapper

    return decorator


# Performance metrics collection
class PerformanceMetrics:
    """Collect and aggregate performance metrics"""

    def __init__(self):
        self.metrics = []

    def record_metric(self, name: str, value: float, tags: dict | None = None):
        """Record a performance metric"""
        metric = {
            "name": name,
            "value": value,
            "timestamp": timezone.now().isoformat(),
            "tags": tags or {},
        }
        self.metrics.append(metric)

        # Log the metric
        logger.info(f"Performance metric: {name} = {value}", extra=metric)

    def get_metrics(self, name: str | None = None) -> list[dict]:
        """Get recorded metrics, optionally filtered by name"""
        if name:
            return [m for m in self.metrics if m["name"] == name]
        return self.metrics.copy()

    def clear_metrics(self):
        """Clear all recorded metrics"""
        self.metrics.clear()


# Global performance metrics instance
performance_metrics = PerformanceMetrics()
