"""
Base Repository Pattern Implementation (DEPRECATED)

⚠️ WARNING: This module is deprecated. Use base_async.py instead.

Migration Guide:
- For new code, use: from src.repositories.base_async import AsyncBaseRepository
- AsyncBaseRepository provides better performance with async/await support
- This sync version will be removed in a future release

Provides generic CRUD operations with filtering, sorting, and pagination support.
"""

from typing import Generic, TypeVar, Type, Optional, List, Dict, Any, Tuple
from sqlmodel import Session, SQLModel, select, func, col
from sqlalchemy import asc, desc, event
from sqlalchemy.orm import selectinload, joinedload
import logging
import time

logger = logging.getLogger(__name__)

# 泛型 type for the model
T = TypeVar("T", bound=SQLModel)


class BaseRepository(Generic[T]):
    """ 泛型 base repository providing common CRUD operations.
    
    Features:
    - Generic CRUD operations (Create, Read, Update, Delete)
    - Filtering support with multiple operators
    - Sorting support (ascending/descending)
    - Pagination support
    - Soft delete support
    - Count operations
    - Batch operations
    
    Usage:
        class ProjectRepository(BaseRepository[ProjectDB]):
            def __init__(self, session: Session):
                super().__init__(ProjectDB, session)
    """
    
    def __init__(self, model: Type[T], session: Session):
        """ Initialize repository with model class and database session.
        
        Args:
            model: SQLModel class for this repository
            session: Database session
        """
        self.model = model
        self.session = session
        self._query_count = 0
        self._total_query_time = 0.0
    
    def get_query_stats(self) -> Dict[str, Any]:
        """ Get query statistics for this repository instance.
        
        Returns:
            Dictionary with query count and total time
        """ 
        return {
            "query_count": self._query_count,
            "total_query_time": self._total_query_time,
            "avg_query_time": self._total_query_time / self._query_count if self._query_count > 0 else 0
        }
    
    def _track_query(self, query_func):
        """ 装饰器 to track query execution time.
        
        Args:
            query_func: Function that executes a query
            
        Returns:
            Wrapped function with timing
        """
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = query_func(*args, **kwargs)
            elapsed = time.time() - start_time
            
            self._query_count += 1
            self._total_query_time += elapsed
            
            if elapsed > 1.0:  # 日志 slow queries
                logger.warning(
                    f"Slow query in {self.model.__name__} repository: {elapsed:.2f}s",
                    extra={"query_time": elapsed, "model": self.model.__name__}
                )
            
            return result
        return wrapper
    
    def get(self, id: str) -> Optional[T]:
        """ Get a single record by ID.
        
        Args:
            id: Primary key value
            
        Returns:
            Model instance or None if not found
        """ 
        start_time = time.time()
        result = self.session.get(self.model, id)
        
        elapsed = time.time() - start_time
        self._query_count += 1
        self._total_query_time += elapsed
        
        return result
    
    def get_with_relations(self, id: str, eager_load: List[str]) -> Optional[T]:
        """ Get a single record by ID with eager loaded relationships.
        Prevents N+1 queries when accessing related data.
        
        Args:
            id: Primary key value
            eager_load: List of relationship names to eager load
            
        Returns:
            Model instance or None if not found
        """ 
        start_time = time.time()
        
        statement = select(self.model).where(
            getattr(self.model, self.model.__table__.primary_key.columns.keys()[0]) == id
        )
        
        # Apply eager loading
        for relationship in eager_load:
            if hasattr(self.model, relationship):
                statement = statement.options(selectinload(getattr(self.model, relationship)))
            else:
                logger.warning(f"Relationship {relationship} not found in {self.model.__name__}")
        
        result = self.session.exec(statement).first()
        
        elapsed = time.time() - start_time
        self._query_count += 1
        self._total_query_time += elapsed
        
        if elapsed > 1.0:
            logger.warning(
                f"Slow query in {self.model.__name__}.get_with_relations(): {elapsed:.2f}s",
                extra={"query_time": elapsed, "model": self.model.__name__, "id": id}
            )
        
        return result
    
    def get_by_field(self, field_name: str, value: Any) -> Optional[T]:
        """ Get a single record by a specific field value.
        
        Args:
            field_name: Name of the field to filter by
            value: Value to match
            
        Returns:
            First matching model instance or None
        """
        statement = select(self.model).where(getattr(self.model, field_name) == value)
        return self.session.exec(statement).first()
    
    def list(
        self,
        skip: int = 0,
        limit: int = 100,
        filters: Optional[Dict[str, Any]] = None,
        sort_by: Optional[str] = None,
        sort_order: str = "asc",
        eager_load: Optional[List[str]] = None
    ) -> List[T]:
        """ List records with optional filtering, sorting, and pagination.
        
        Args:
            skip: Number of records to skip (offset)
            limit: Maximum number of records to return
            filters: Dictionary of field:value pairs for filtering
            sort_by: Field name to sort by
            sort_order: Sort order ('asc' or 'desc')
            eager_load: List of relationship names to eager load (prevents N+1 queries)
            
        Returns:
            List of model instances
        """ 
        start_time = time.time()
        
        statement = select(self.model)
        
        # Apply eager loading to prevent N+1 queries
        if eager_load:
            for relationship in eager_load:
                if hasattr(self.model, relationship):
                    statement = statement.options(selectinload(getattr(self.model, relationship)))
                else:
                    logger.warning(f"Relationship {relationship} not found in {self.model.__name__}")
        
        # Apply filters
        if filters:
            statement = self._apply_filters(statement, filters)
        
        # Apply sorting
        if sort_by:
            sort_column = getattr(self.model, sort_by)
            if sort_order.lower() == "desc":
                statement = statement.order_by(desc(sort_column))
            else:
                statement = statement.order_by(asc(sort_column))
        
        # Apply pagination
        statement = statement.offset(skip).limit(limit)
        
        result = list(self.session.exec(statement).all())
        
        # Track query performance
        elapsed = time.time() - start_time
        self._query_count += 1
        self._total_query_time += elapsed
        
        if elapsed > 1.0:
            logger.warning(
                f"Slow query in {self.model.__name__}.list(): {elapsed:.2f}s",
                extra={
                    "query_time": elapsed,
                    "model": self.model.__name__,
                    "filters": filters,
                    "limit": limit
                }
            )
        
        return result
    
    def list_paginated(
        self,
        page: int = 1,
        page_size: int = 20,
        filters: Optional[Dict[str, Any]] = None,
        sort_by: Optional[str] = None,
        sort_order: str = "asc",
        eager_load: Optional[List[str]] = None
    ) -> Tuple[List[T], int]:
        """ List records with pagination metadata.
        
        Args:
            page: Page number (1-indexed)
            page_size: Number of records per page
            filters: Dictionary of field:value pairs for filtering
            sort_by: Field name to sort by
            sort_order: Sort order ('asc' or 'desc')
            eager_load: List of relationship names to eager load (prevents N+1 queries)
            
        Returns:
            Tuple of (records, total_count)
        """
        # Calculate offset
        skip = (page - 1) * page_size
        
        # Get records
        records = self.list(
            skip=skip,
            limit=page_size,
            filters=filters,
            sort_by=sort_by,
            sort_order=sort_order,
            eager_load=eager_load
        )
        
        # Get total count
        total = self.count(filters=filters)
        
        return records, total
    
    def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
        """ 计数 records with optional filtering.
        
        Args:
            filters: Dictionary of field:value pairs for filtering
            
        Returns:
            Number of matching records
        """ 
        start_time = time.time()
        
        statement = select(func.count()).select_from(self.model)
        
        if filters:
            statement = self._apply_filters(statement, filters)
        
        result = self.session.exec(statement).one()
        
        # Track query performance
        elapsed = time.time() - start_time
        self._query_count += 1
        self._total_query_time += elapsed
        
        return result
    
    def create(self, obj: T) -> T:
        """ Create a new record.
        
        Args:
            obj: Model instance to create
            
        Returns:
            Created model instance with updated fields
        """
        self.session.add(obj)
        self.session.commit()
        self.session.refresh(obj)
        return obj
    
    def create_many(self, objects: List[T]) -> List[T]:
        """ Create multiple records in a single transaction.
        
        Args:
            objects: List of model instances to create
            
        Returns:
            List of created model instances
        """
        for obj in objects:
            self.session.add(obj)
        self.session.commit()
        
        for obj in objects:
            self.session.refresh(obj)
        
        return objects
    
    def update(self, obj: T) -> T:
        """ 更新 an existing record.
        
        Args:
            obj: Model instance with updated values
            
        Returns:
            Updated model instance
        """
        self.session.add(obj)
        self.session.commit()
        self.session.refresh(obj)
        return obj
    
    def update_by_id(self, id: str, data: Dict[str, Any]) -> Optional[T]:
        """ 更新 a record by ID with partial data.
        
        Args:
            id: Primary key value
            data: Dictionary of fields to update
            
        Returns:
            Updated model instance or None if not found
        """
        obj = self.get(id)
        if not obj:
            return None
        
        for key, value in data.items():
            if hasattr(obj, key):
                setattr(obj, key, value)
        
        return self.update(obj)
    
    def delete(self, id: str) -> bool:
        """ 删除 a record by ID (hard delete).
        
        Args:
            id: Primary key value
            
        Returns:
            True if deleted, False if not found
        """
        obj = self.get(id)
        if not obj:
            return False
        
        self.session.delete(obj)
        self.session.commit()
        return True
    
    def soft_delete(self, id: str) -> bool:
        """
        Soft delete a record by setting deleted_at timestamp.
        Only works if model has a deleted_at field.
        
        Args:
            id: Primary key value
            
        Returns:
            True if soft deleted, False if not found or no deleted_at field
        """
        obj = self.get(id)
        if not obj:
            return False
        
        if not hasattr(obj, "deleted_at"):
            logger.warning(f"Model {self.model.__name__} does not support soft delete")
            return False
        
        from datetime import datetime
        obj.deleted_at = datetime.now().timestamp()
        self.update(obj)
        return True
    
    def exists(self, id: str) -> bool:
        """
        Check if a record exists by ID.
        
        Args:
            id: Primary key value
            
        Returns:
            True if exists, False otherwise
        """ 
        return self.get(id) is not None
    
    def exists_by_field(self, field_name: str, value: Any) -> bool:
        """
        Check if a record exists with a specific field value.
        
        Args:
            field_name: Name of the field to check
            value: Value to match
            
        Returns:
            True if exists, False otherwise
        """
        statement = select(func.count()).select_from(self.model).where(
            getattr(self.model, field_name) == value
        )
        count = self.session.exec(statement).one()
        return count > 0
    
    def _apply_filters(self, statement, filters: Dict[str, Any]):
        """
        Apply filters to a select statement.
        
        Supports:
        - Simple equality: {"name": "value"}
        - Operators: {"name__like": "%value%", "age__gt": 18}
        - None/null checks: {"deleted_at": None}
        - List membership: {"status__in": ["active", "pending"]}
        
        Args:
            statement: SQLAlchemy select statement
            filters: Dictionary of filters
            
        Returns:
            Modified select statement
        """
        for key, value in filters.items():
            # 解析 field name and operator
            if "__" in key:
                field_name, operator = key.split("__", 1)
            else:
                field_name = key
                operator = "eq"
            
            # Get the model field
            if not hasattr(self.model, field_name):
                logger.warning(f"Field {field_name} not found in {self.model.__name__}")
                continue
            
            field = getattr(self.model, field_name)
            
            # Apply operator
            if operator == "eq":
                if value is None:
                    statement = statement.where(field.is_(None))
                else:
                    statement = statement.where(field == value)
            elif operator == "ne":
                if value is None:
                    statement = statement.where(field.is_not(None))
                else:
                    statement = statement.where(field != value)
            elif operator == "gt":
                statement = statement.where(field > value)
            elif operator == "gte":
                statement = statement.where(field >= value)
            elif operator == "lt":
                statement = statement.where(field < value)
            elif operator == "lte":
                statement = statement.where(field <= value)
            elif operator == "like":
                statement = statement.where(field.like(value))
            elif operator == "ilike":
                statement = statement.where(field.ilike(value))
            elif operator == "in":
                statement = statement.where(field.in_(value))
            elif operator == "not_in":
                statement = statement.where(~field.in_(value))
            else:
                logger.warning(f"Unknown operator: {operator}")
        
        return statement
