"""
Search call tracking system for metrics collection.
Similar to token_counter.py but tracks search engine usage.
"""

from typing import Any, Dict, List, Optional

from loguru import logger
from sqlalchemy import case, func

from ..utilities.thread_context import get_search_context
from ..database.models import SearchCall
from .database import MetricsDatabase
from .query_utils import get_research_mode_condition, get_time_filter_condition


class SearchTracker:
    """Track search engine calls and performance metrics."""

    def __init__(self, db: Optional[MetricsDatabase] = None):
        """Initialize the search tracker."""
        self.db = db or MetricsDatabase()

    @staticmethod
    def record_search(
        engine_name: str,
        query: str,
        results_count: int = 0,
        response_time_ms: int = 0,
        success: bool = True,
        error_message: Optional[str] = None,
    ) -> None:
        """Record a completed search operation directly to database."""

        # Extract research context (thread-safe)
        context = get_search_context()

        # Skip metrics recording in programmatic mode or when no context is set
        if context is None:
            logger.warning(
                "Skipping search metrics recording - no research context available "
                "(likely in programmatic mode)"
            )
            return

        research_id = context.get("research_id")

        # Convert research_id to string if it's an integer (for backward compatibility)
        if isinstance(research_id, int):
            research_id = str(research_id)
        research_query = context.get("research_query")
        research_mode = context.get("research_mode", "unknown")
        research_phase = context.get("research_phase", "search")
        search_iteration = context.get("search_iteration", 0)

        # Determine success status
        success_status = "success" if success else "error"
        error_type = None
        if error_message:
            error_type = (
                type(error_message).__name__
                if isinstance(error_message, Exception)
                else "unknown_error"
            )

        # Record search call in database - only from background threads
        try:
            # Get username from context for thread-safe database
            username = context.get("username")
            if not username:
                logger.warning(
                    f"Cannot save search metrics - no username in research context. "
                    f"Search: {engine_name} for '{query}'"
                )
                return

            # Get password from context
            password = context.get("user_password")
            if not password:
                logger.warning(
                    f"Cannot save search metrics - no password in research context. "
                    f"Search: {engine_name} for '{query}', username: {username}"
                )
                return

            # Use thread-safe metrics writer
            from ..database.thread_metrics import metrics_writer

            try:
                # Set password for this thread
                metrics_writer.set_user_password(username, password)

                with metrics_writer.get_session(username) as session:
                    search_call = SearchCall(
                        research_id=research_id,
                        research_query=research_query,
                        research_mode=research_mode,
                        research_phase=research_phase,
                        search_iteration=search_iteration,
                        search_engine=engine_name,
                        query=query,
                        results_count=results_count,
                        response_time_ms=response_time_ms,
                        success_status=success_status,
                        error_type=error_type,
                        error_message=str(error_message)
                        if error_message
                        else None,
                    )
                    session.add(search_call)

                    logger.debug(
                        f"Search call recorded to encrypted DB: {engine_name} - "
                        f"{results_count} results in {response_time_ms}ms"
                    )
            except Exception:
                logger.exception("Failed to write search metrics")

        except Exception:
            logger.exception("Failed to record search call")

    def get_search_metrics(
        self,
        period: str = "30d",
        research_mode: str = "all",
        username: Optional[str] = None,
        password: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Get search engine usage metrics."""
        with self.db.get_session(
            username=username, password=password
        ) as session:
            try:
                # Build base query with filters
                query = session.query(SearchCall).filter(
                    SearchCall.search_engine.isnot(None)
                )

                # Apply time filter
                time_condition = get_time_filter_condition(
                    period, SearchCall.timestamp
                )
                if time_condition is not None:
                    query = query.filter(time_condition)

                # Apply research mode filter
                mode_condition = get_research_mode_condition(
                    research_mode, SearchCall.research_mode
                )
                if mode_condition is not None:
                    query = query.filter(mode_condition)

                # Get search engine statistics using ORM aggregation
                search_stats = session.query(
                    SearchCall.search_engine,
                    func.count().label("call_count"),
                    func.avg(SearchCall.response_time_ms).label(
                        "avg_response_time"
                    ),
                    func.sum(SearchCall.results_count).label("total_results"),
                    func.avg(SearchCall.results_count).label(
                        "avg_results_per_call"
                    ),
                    func.sum(
                        case(
                            (SearchCall.success_status == "success", 1), else_=0
                        )
                    ).label("success_count"),
                    func.sum(
                        case((SearchCall.success_status == "error", 1), else_=0)
                    ).label("error_count"),
                ).filter(SearchCall.search_engine.isnot(None))

                # Apply same filters to stats query
                if time_condition is not None:
                    search_stats = search_stats.filter(time_condition)
                if mode_condition is not None:
                    search_stats = search_stats.filter(mode_condition)

                search_stats = (
                    search_stats.group_by(SearchCall.search_engine)
                    .order_by(func.count().desc())
                    .all()
                )

                # Get recent search calls
                recent_calls_query = session.query(SearchCall)
                if time_condition is not None:
                    recent_calls_query = recent_calls_query.filter(
                        time_condition
                    )
                if mode_condition is not None:
                    recent_calls_query = recent_calls_query.filter(
                        mode_condition
                    )

                recent_calls = (
                    recent_calls_query.order_by(SearchCall.timestamp.desc())
                    .limit(20)
                    .all()
                )

                return {
                    "search_engine_stats": [
                        {
                            "engine": stat.search_engine,
                            "call_count": stat.call_count,
                            "avg_response_time": stat.avg_response_time or 0,
                            "total_results": stat.total_results or 0,
                            "avg_results_per_call": stat.avg_results_per_call
                            or 0,
                            "success_rate": (
                                (stat.success_count / stat.call_count * 100)
                                if stat.call_count > 0
                                else 0
                            ),
                            "error_count": stat.error_count or 0,
                        }
                        for stat in search_stats
                    ],
                    "recent_calls": [
                        {
                            "engine": call.search_engine,
                            "query": (
                                call.query[:100] + "..."
                                if len(call.query or "") > 100
                                else call.query
                            ),
                            "results_count": call.results_count,
                            "response_time_ms": call.response_time_ms,
                            "success_status": call.success_status,
                            "timestamp": str(call.timestamp),
                        }
                        for call in recent_calls
                    ],
                }

            except Exception:
                logger.exception("Error getting search metrics")
                return {"search_engine_stats": [], "recent_calls": []}

    def get_research_search_metrics(
        self,
        research_id: str,
        username: Optional[str] = None,
        password: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Get search metrics for a specific research session."""
        with self.db.get_session(
            username=username, password=password
        ) as session:
            try:
                # Get all search calls for this research
                search_calls = (
                    session.query(SearchCall)
                    .filter(SearchCall.research_id == research_id)
                    .order_by(SearchCall.timestamp.asc())
                    .all()
                )

                # Get search engine stats for this research
                engine_stats = (
                    session.query(
                        SearchCall.search_engine,
                        func.count().label("call_count"),
                        func.avg(SearchCall.response_time_ms).label(
                            "avg_response_time"
                        ),
                        func.sum(SearchCall.results_count).label(
                            "total_results"
                        ),
                        func.sum(
                            case(
                                (SearchCall.success_status == "success", 1),
                                else_=0,
                            )
                        ).label("success_count"),
                    )
                    .filter(SearchCall.research_id == research_id)
                    .group_by(SearchCall.search_engine)
                    .order_by(func.count().desc())
                    .all()
                )

                # Calculate totals
                total_searches = len(search_calls)
                total_results = sum(
                    call.results_count or 0 for call in search_calls
                )
                avg_response_time = (
                    sum(call.response_time_ms or 0 for call in search_calls)
                    / total_searches
                    if total_searches > 0
                    else 0
                )
                successful_searches = sum(
                    1
                    for call in search_calls
                    if call.success_status == "success"
                )
                success_rate = (
                    (successful_searches / total_searches * 100)
                    if total_searches > 0
                    else 0
                )

                return {
                    "total_searches": total_searches,
                    "total_results": total_results,
                    "avg_response_time": round(avg_response_time),
                    "success_rate": round(success_rate, 1),
                    "search_calls": [
                        {
                            "engine": call.search_engine,
                            "query": call.query,
                            "results_count": call.results_count,
                            "response_time_ms": call.response_time_ms,
                            "success_status": call.success_status,
                            "timestamp": str(call.timestamp),
                        }
                        for call in search_calls
                    ],
                    "engine_stats": [
                        {
                            "engine": stat.search_engine,
                            "call_count": stat.call_count,
                            "avg_response_time": stat.avg_response_time or 0,
                            "total_results": stat.total_results or 0,
                            "success_rate": (
                                (stat.success_count / stat.call_count * 100)
                                if stat.call_count > 0
                                else 0
                            ),
                        }
                        for stat in engine_stats
                    ],
                }

            except Exception:
                logger.exception("Error getting research search metrics")
                return {
                    "total_searches": 0,
                    "total_results": 0,
                    "avg_response_time": 0,
                    "success_rate": 0,
                    "search_calls": [],
                    "engine_stats": [],
                }

    def get_search_time_series(
        self,
        period: str = "30d",
        research_mode: str = "all",
        username: Optional[str] = None,
        password: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """Get search activity time series data for charting.

        Args:
            period: Time period to filter by ('7d', '30d', '3m', '1y', 'all')
            research_mode: Research mode to filter by ('quick', 'detailed', 'all')
            username: Username for database access
            password: Password for database access

        Returns:
            List of time series data points with search engine activity
        """
        with self.db.get_session(
            username=username, password=password
        ) as session:
            try:
                # Build base query
                query = session.query(SearchCall).filter(
                    SearchCall.search_engine.isnot(None),
                    SearchCall.timestamp.isnot(None),
                )

                # Apply time filter
                time_condition = get_time_filter_condition(
                    period, SearchCall.timestamp
                )
                if time_condition is not None:
                    query = query.filter(time_condition)

                # Apply research mode filter
                mode_condition = get_research_mode_condition(
                    research_mode, SearchCall.research_mode
                )
                if mode_condition is not None:
                    query = query.filter(mode_condition)

                # Get all search calls ordered by time
                search_calls = query.order_by(SearchCall.timestamp.asc()).all()

                # Create time series data
                time_series = []
                for call in search_calls:
                    time_series.append(
                        {
                            "timestamp": (
                                str(call.timestamp) if call.timestamp else None
                            ),
                            "search_engine": call.search_engine,
                            "results_count": call.results_count or 0,
                            "response_time_ms": call.response_time_ms or 0,
                            "success_status": call.success_status,
                            "query": (
                                call.query[:50] + "..."
                                if call.query and len(call.query) > 50
                                else call.query
                            ),
                        }
                    )

                return time_series

            except Exception:
                logger.exception("Error getting search time series")
                return []


def get_search_tracker() -> SearchTracker:
    """Create a SearchTracker instance.

    Returns a fresh instance each time. Callers should pass username/password
    to the query methods so the correct per-user database is accessed.
    """
    return SearchTracker()
