"""
Database query optimization utilities.
Helps identify and fix N+1 query problems.
"""

from typing import List, Optional, Type

from sqlalchemy import inspect
from sqlalchemy.orm import Query, joinedload, selectinload, subqueryload

from app import db


def eager_load_relations(query: Query, model_class: Type, relations: List[str], strategy: str = "joined") -> Query:
    """
    Eagerly load relations to prevent N+1 queries.

    Args:
        query: SQLAlchemy query
        model_class: Model class
        relations: List of relation names to load
        strategy: Loading strategy ('joined', 'selectin', 'subquery')

    Returns:
        Query with eager loading options
    """
    loader_map = {"joined": joinedload, "selectin": selectinload, "subquery": subqueryload}

    loader_func = loader_map.get(strategy, joinedload)

    for relation in relations:
        if hasattr(model_class, relation):
            query = query.options(loader_func(getattr(model_class, relation)))

    return query


def get_model_relations(model_class: Type) -> List[str]:
    """
    Get all relation names for a model.

    Args:
        model_class: SQLAlchemy model class

    Returns:
        List of relation attribute names
    """
    inspector = inspect(model_class)
    return [rel.key for rel in inspector.relationships]


def optimize_list_query(query: Query, model_class: Type, common_relations: Optional[List[str]] = None) -> Query:
    """
    Optimize a list query by eagerly loading common relations.

    Args:
        query: SQLAlchemy query
        model_class: Model class
        common_relations: Optional list of relations to always load

    Returns:
        Optimized query
    """
    if common_relations:
        return eager_load_relations(query, model_class, common_relations)

    # Auto-detect common relations (relationships that are likely to be accessed)
    all_relations = get_model_relations(model_class)

    # Common patterns: user, project, client, task, etc.
    common_patterns = ["user", "project", "client", "task", "assignee", "creator"]
    relations_to_load = [rel for rel in all_relations if any(pattern in rel.lower() for pattern in common_patterns)]

    if relations_to_load:
        return eager_load_relations(query, model_class, relations_to_load)

    return query


def batch_load_relations(items: List[Type], relation_name: str, model_class: Type) -> None:
    """
    Batch load a relation for a list of items (prevents N+1).

    Note: This is a helper for cases where eager loading wasn't possible.
    Prefer using eager_load_relations in the query instead.

    Args:
        items: List of model instances
        relation_name: Name of relation to load
        model_class: Model class
    """
    if not items:
        return

    # Get IDs
    ids = [item.id for item in items]

    # Load all related items in one query
    relation = getattr(model_class, relation_name)
    related_items = (
        db.session.query(relation.property.mapper.class_).filter(relation.property.mapper.class_.id.in_(ids)).all()
    )

    # This is a simplified example - in practice, you'd need to map them back


class QueryProfiler:
    """Helper class to profile and optimize queries"""

    @staticmethod
    def count_queries(func):
        """Decorator to count database queries in a function"""
        from functools import wraps

        from sqlalchemy import event
        from sqlalchemy.engine import Engine

        @wraps(func)
        def wrapper(*args, **kwargs):
            queries = []

            def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
                queries.append(statement)

            event.listen(Engine, "before_cursor_execute", before_cursor_execute)

            try:
                result = func(*args, **kwargs)
                return result, len(queries)
            finally:
                event.remove(Engine, "before_cursor_execute", before_cursor_execute)

        return wrapper
