"""Routes for metrics dashboard."""

from datetime import datetime, timedelta, UTC
from typing import Any
from urllib.parse import urlparse

from flask import Blueprint, jsonify, request, session as flask_session
from loguru import logger
from sqlalchemy import case, func

from ...database.models import (
    RateLimitAttempt,
    RateLimitEstimate,
    Research,
    ResearchHistory,
    ResearchRating,
    ResearchResource,
    ResearchStrategy,
    TokenUsage,
)
from ...constants import get_available_strategies
from ...domain_classifier import DomainClassifier, DomainClassification
from ...database.session_context import get_user_db_session
from ...metrics import TokenCounter
from ...metrics.query_utils import get_period_days, get_time_filter_condition
from ...metrics.search_tracker import get_search_tracker
from ...web_search_engines.rate_limiting import get_tracker
from ...security.decorators import require_json_body
from ..auth.decorators import login_required
from ..utils.templates import render_template_with_defaults

# Create a Blueprint for metrics
metrics_bp = Blueprint("metrics", __name__, url_prefix="/metrics")

# NOTE: Routes use flask_session["username"] (not .get()) intentionally.
# @login_required guarantees the key exists; direct access fails fast
# if the decorator is ever removed.


def _extract_domain(url):
    """Extract normalized domain from URL, stripping www. prefix."""
    try:
        parsed = urlparse(url)
        domain = parsed.netloc.lower()
        if domain.startswith("www."):
            domain = domain[4:]
        return domain if domain else None
    except (ValueError, AttributeError, TypeError):
        return None


def get_rating_analytics(period="30d", research_mode="all", username=None):
    """Get rating analytics for the specified period and research mode."""
    try:
        if not username:
            username = flask_session.get("username")

        if not username:
            return {
                "rating_analytics": {
                    "avg_rating": None,
                    "total_ratings": 0,
                    "rating_distribution": {},
                    "satisfaction_stats": {
                        "very_satisfied": 0,
                        "satisfied": 0,
                        "neutral": 0,
                        "dissatisfied": 0,
                        "very_dissatisfied": 0,
                    },
                    "error": "No user session",
                }
            }

        # Calculate date range
        days = get_period_days(period)

        with get_user_db_session(username) as session:
            query = session.query(ResearchRating)

            # Apply time filter
            if days:
                cutoff_date = datetime.now(UTC) - timedelta(days=days)
                query = query.filter(ResearchRating.created_at >= cutoff_date)

            # Get all ratings
            ratings = query.all()

            if not ratings:
                return {
                    "rating_analytics": {
                        "avg_rating": None,
                        "total_ratings": 0,
                        "rating_distribution": {},
                        "satisfaction_stats": {
                            "very_satisfied": 0,
                            "satisfied": 0,
                            "neutral": 0,
                            "dissatisfied": 0,
                            "very_dissatisfied": 0,
                        },
                    }
                }

            # Calculate statistics
            rating_values = [r.rating for r in ratings]
            avg_rating = sum(rating_values) / len(rating_values)

            # Rating distribution
            rating_counts = {}
            for i in range(1, 6):
                rating_counts[str(i)] = rating_values.count(i)

            # Satisfaction categories
            satisfaction_stats = {
                "very_satisfied": rating_values.count(5),
                "satisfied": rating_values.count(4),
                "neutral": rating_values.count(3),
                "dissatisfied": rating_values.count(2),
                "very_dissatisfied": rating_values.count(1),
            }

            return {
                "rating_analytics": {
                    "avg_rating": round(avg_rating, 1),
                    "total_ratings": len(ratings),
                    "rating_distribution": rating_counts,
                    "satisfaction_stats": satisfaction_stats,
                }
            }

    except Exception:
        logger.exception("Error getting rating analytics")
        return {
            "rating_analytics": {
                "avg_rating": None,
                "total_ratings": 0,
                "rating_distribution": {},
                "satisfaction_stats": {
                    "very_satisfied": 0,
                    "satisfied": 0,
                    "neutral": 0,
                    "dissatisfied": 0,
                    "very_dissatisfied": 0,
                },
            }
        }


def get_link_analytics(period="30d", username=None):
    """Get link analytics from research resources."""
    try:
        if not username:
            username = flask_session.get("username")

        if not username:
            return {
                "link_analytics": {
                    "top_domains": [],
                    "total_unique_domains": 0,
                    "avg_links_per_research": 0,
                    "domain_distribution": {},
                    "source_type_analysis": {},
                    "academic_vs_general": {},
                    "total_links": 0,
                    "error": "No user session",
                }
            }

        # Calculate date range
        days = get_period_days(period)

        with get_user_db_session(username) as session:
            # Base query
            query = session.query(ResearchResource)

            # Apply time filter
            if days:
                cutoff_date = datetime.now(UTC) - timedelta(days=days)
                query = query.filter(
                    ResearchResource.created_at >= cutoff_date.isoformat()
                )

            # Get all resources
            resources = query.all()

            if not resources:
                return {
                    "link_analytics": {
                        "top_domains": [],
                        "total_unique_domains": 0,
                        "avg_links_per_research": 0,
                        "domain_distribution": {},
                        "source_type_analysis": {},
                        "academic_vs_general": {},
                        "total_links": 0,
                    }
                }

            # Extract domains from URLs
            domain_counts: dict[str, Any] = {}
            domain_researches: dict[
                str, Any
            ] = {}  # Track which researches used each domain
            source_types: dict[str, Any] = {}
            temporal_data: dict[str, Any] = {}  # Track links over time
            domain_connections: dict[
                str, Any
            ] = {}  # Track domain co-occurrences

            # Generic category counting from LLM classifications
            category_counts: dict[str, Any] = {}

            quality_metrics = {
                "with_title": 0,
                "with_preview": 0,
                "with_both": 0,
                "total": 0,
            }

            # First pass: collect all domains from resources
            all_domains = set()
            for resource in resources:
                if resource.url:
                    domain = _extract_domain(resource.url)
                    if domain:
                        all_domains.add(domain)

            # Batch load all domain classifications in one query (fix N+1)
            domain_classifications_map = {}
            if all_domains:
                all_classifications = (
                    session.query(DomainClassification)
                    .filter(DomainClassification.domain.in_(all_domains))
                    .all()
                )
                for classification in all_classifications:
                    domain_classifications_map[classification.domain] = (
                        classification
                    )

            # Second pass: process resources with pre-loaded classifications
            for resource in resources:
                if resource.url:
                    try:
                        domain = _extract_domain(resource.url)
                        if not domain:
                            continue

                        # Count domains
                        domain_counts[domain] = domain_counts.get(domain, 0) + 1

                        # Track research IDs for each domain
                        if domain not in domain_researches:
                            domain_researches[domain] = set()
                        domain_researches[domain].add(resource.research_id)

                        # Track temporal data (daily counts)
                        if resource.created_at:
                            date_str = resource.created_at[
                                :10
                            ]  # Extract YYYY-MM-DD
                            temporal_data[date_str] = (
                                temporal_data.get(date_str, 0) + 1
                            )

                        # Count categories from pre-loaded classifications (no N+1)
                        classification = domain_classifications_map.get(domain)
                        if classification:
                            category = classification.category
                            category_counts[category] = (
                                category_counts.get(category, 0) + 1
                            )
                        else:
                            category_counts["Unclassified"] = (
                                category_counts.get("Unclassified", 0) + 1
                            )

                        # Track source type from metadata if available
                        if resource.source_type:
                            source_types[resource.source_type] = (
                                source_types.get(resource.source_type, 0) + 1
                            )

                        # Track quality metrics
                        quality_metrics["total"] += 1
                        if resource.title:
                            quality_metrics["with_title"] += 1
                        if resource.content_preview:
                            quality_metrics["with_preview"] += 1
                        if resource.title and resource.content_preview:
                            quality_metrics["with_both"] += 1

                        # Track domain co-occurrences for network visualization
                        research_id = resource.research_id
                        if research_id not in domain_connections:
                            domain_connections[research_id] = []
                        domain_connections[research_id].append(domain)

                    except Exception:
                        logger.warning(f"Error parsing URL {resource.url}")

            # Sort domains by count and get top 10
            sorted_domains = sorted(
                domain_counts.items(), key=lambda x: x[1], reverse=True
            )
            top_10_domains = sorted_domains[:10]

            # Calculate domain distribution (top domains vs others)
            top_10_count = sum(count for _, count in top_10_domains)
            others_count = len(resources) - top_10_count

            # Get unique research IDs to calculate average
            unique_research_ids = {r.research_id for r in resources}
            avg_links = (
                len(resources) / len(unique_research_ids)
                if unique_research_ids
                else 0
            )

            # Prepare temporal trend data (sorted by date)
            temporal_trend = sorted(
                [
                    {"date": date, "count": count}
                    for date, count in temporal_data.items()
                ],
                key=lambda x: x["date"],
            )

            # Get most recent research for each top domain and classifications
            domain_recent_research = {}
            # Build domain_classifications dict from pre-loaded data
            domain_classifications = {
                domain: {
                    "category": classification.category,
                    "subcategory": classification.subcategory,
                    "confidence": classification.confidence,
                }
                for domain, classification in domain_classifications_map.items()
            }

            # Batch-load research details for top domains (fix N+1 query)
            all_research_ids = []
            domain_research_id_lists = {}
            for domain, _ in top_10_domains:
                if domain in domain_researches:
                    ids = list(domain_researches[domain])[:3]
                    domain_research_id_lists[domain] = ids
                    all_research_ids.extend(ids)

            research_by_id = {}
            if all_research_ids:
                researches = (
                    session.query(ResearchHistory)
                    .filter(ResearchHistory.id.in_(all_research_ids))
                    .all()
                )
                research_by_id = {r.id: r for r in researches}

            for domain, ids in domain_research_id_lists.items():
                domain_recent_research[domain] = [
                    {
                        "id": r_id,
                        "query": research_by_id[r_id].query[:50]
                        if research_by_id.get(r_id)
                        and research_by_id[r_id].query
                        else "Research",
                    }
                    for r_id in ids
                    if r_id in research_by_id
                ]

            return {
                "link_analytics": {
                    "top_domains": [
                        {
                            "domain": domain,
                            "count": count,
                            "percentage": round(
                                count / len(resources) * 100, 1
                            ),
                            "research_count": len(
                                domain_researches.get(domain, set())
                            ),
                            "recent_researches": domain_recent_research.get(
                                domain, []
                            ),
                            "classification": domain_classifications.get(
                                domain, None
                            ),
                        }
                        for domain, count in top_10_domains
                    ],
                    "total_unique_domains": len(domain_counts),
                    "avg_links_per_research": round(avg_links, 1),
                    "domain_distribution": {
                        "top_10": top_10_count,
                        "others": others_count,
                    },
                    "source_type_analysis": source_types,
                    "category_distribution": category_counts,
                    # Generic pie chart data - use whatever LLM classifier outputs
                    "domain_categories": category_counts,
                    "total_links": len(resources),
                    "total_researches": len(unique_research_ids),
                    "temporal_trend": temporal_trend,
                    "domain_metrics": {
                        domain: {
                            "usage_count": count,
                            "usage_percentage": round(
                                count / len(resources) * 100, 1
                            ),
                            "research_diversity": len(
                                domain_researches.get(domain, set())
                            ),
                            "frequency_rank": rank + 1,
                        }
                        for rank, (domain, count) in enumerate(top_10_domains)
                    },
                }
            }

    except Exception:
        logger.exception("Error getting link analytics")
        return {
            "link_analytics": {
                "top_domains": [],
                "total_unique_domains": 0,
                "avg_links_per_research": 0,
                "domain_distribution": {},
                "source_type_analysis": {},
                "academic_vs_general": {},
                "total_links": 0,
                "error": "Failed to retrieve link analytics",
            }
        }


def get_strategy_analytics(period="30d", username=None):
    """Get strategy usage analytics for the specified period."""
    try:
        if not username:
            username = flask_session.get("username")

        if not username:
            return {
                "strategy_analytics": {
                    "total_research_with_strategy": 0,
                    "total_research": 0,
                    "most_popular_strategy": None,
                    "strategy_usage": [],
                    "strategy_distribution": {},
                    "available_strategies": get_available_strategies(),
                    "error": "No user session",
                }
            }

        # Calculate date range
        days = get_period_days(period)

        with get_user_db_session(username) as session:
            # Check if we have any ResearchStrategy records
            strategy_count = session.query(ResearchStrategy).count()

            if strategy_count == 0:
                logger.warning("No research strategies found in database")
                return {
                    "strategy_analytics": {
                        "total_research_with_strategy": 0,
                        "total_research": 0,
                        "most_popular_strategy": None,
                        "strategy_usage": [],
                        "strategy_distribution": {},
                        "available_strategies": get_available_strategies(),
                        "message": "Strategy tracking not yet available - run a research to start tracking",
                    }
                }

            # Base query for strategy usage (no JOIN needed since we just want strategy counts)
            query = session.query(
                ResearchStrategy.strategy_name,
                func.count(ResearchStrategy.id).label("usage_count"),
            )

            # Apply time filter if specified
            if days:
                cutoff_date = datetime.now(UTC) - timedelta(days=days)
                query = query.filter(ResearchStrategy.created_at >= cutoff_date)

            # Group by strategy and order by usage
            strategy_results = (
                query.group_by(ResearchStrategy.strategy_name)
                .order_by(func.count(ResearchStrategy.id).desc())
                .all()
            )

            # Get total strategy count for percentage calculation
            total_query = session.query(ResearchStrategy)
            if days:
                total_query = total_query.filter(
                    ResearchStrategy.created_at >= cutoff_date
                )
            total_research = total_query.count()

            # Format strategy data
            strategy_usage = []
            strategy_distribution = {}

            for strategy_name, usage_count in strategy_results:
                percentage = (
                    (usage_count / total_research * 100)
                    if total_research > 0
                    else 0
                )
                strategy_usage.append(
                    {
                        "strategy": strategy_name,
                        "count": usage_count,
                        "percentage": round(percentage, 1),
                    }
                )
                strategy_distribution[strategy_name] = usage_count

            # Find most popular strategy
            most_popular = (
                strategy_usage[0]["strategy"] if strategy_usage else None
            )

            return {
                "strategy_analytics": {
                    "total_research_with_strategy": sum(
                        item["count"] for item in strategy_usage
                    ),
                    "total_research": total_research,
                    "most_popular_strategy": most_popular,
                    "strategy_usage": strategy_usage,
                    "strategy_distribution": strategy_distribution,
                    "available_strategies": get_available_strategies(),
                }
            }

    except Exception:
        logger.exception("Error getting strategy analytics")
        return {
            "strategy_analytics": {
                "total_research_with_strategy": 0,
                "total_research": 0,
                "most_popular_strategy": None,
                "strategy_usage": [],
                "strategy_distribution": {},
                "available_strategies": get_available_strategies(),
                "error": "Failed to retrieve strategy data",
            }
        }


def get_rate_limiting_analytics(period="30d", username=None):
    """Get rate limiting analytics for the specified period."""
    try:
        if not username:
            username = flask_session.get("username")

        if not username:
            return {
                "rate_limiting": {
                    "total_attempts": 0,
                    "successful_attempts": 0,
                    "failed_attempts": 0,
                    "success_rate": 0,
                    "rate_limit_events": 0,
                    "avg_wait_time": 0,
                    "avg_successful_wait": 0,
                    "tracked_engines": 0,
                    "engine_stats": [],
                    "total_engines_tracked": 0,
                    "healthy_engines": 0,
                    "degraded_engines": 0,
                    "poor_engines": 0,
                    "error": "No user session",
                }
            }

        # Calculate date range for timestamp filtering
        import time

        if period == "7d":
            cutoff_time = time.time() - (7 * 24 * 3600)
        elif period == "30d":
            cutoff_time = time.time() - (30 * 24 * 3600)
        elif period == "3m":
            cutoff_time = time.time() - (90 * 24 * 3600)
        elif period == "1y":
            cutoff_time = time.time() - (365 * 24 * 3600)
        else:  # all
            cutoff_time = 0

        with get_user_db_session(username) as session:
            # Get rate limit attempts
            rate_limit_query = session.query(RateLimitAttempt)

            # Apply time filter
            if cutoff_time > 0:
                rate_limit_query = rate_limit_query.filter(
                    RateLimitAttempt.timestamp >= cutoff_time
                )

            # Get rate limit statistics
            total_attempts = rate_limit_query.count()
            successful_attempts = rate_limit_query.filter(
                RateLimitAttempt.success
            ).count()
            failed_attempts = total_attempts - successful_attempts

            # Count rate limiting events (failures with RateLimitError)
            rate_limit_events = rate_limit_query.filter(
                ~RateLimitAttempt.success,
                RateLimitAttempt.error_type == "RateLimitError",
            ).count()

            logger.info(
                f"Rate limit attempts in database: total={total_attempts}, successful={successful_attempts}"
            )

            # Get all attempts for detailed calculations
            attempts = rate_limit_query.all()

            # Calculate average wait times
            if attempts:
                avg_wait_time = sum(a.wait_time for a in attempts) / len(
                    attempts
                )
                successful_wait_times = [
                    a.wait_time for a in attempts if a.success
                ]
                avg_successful_wait = (
                    sum(successful_wait_times) / len(successful_wait_times)
                    if successful_wait_times
                    else 0
                )
            else:
                avg_wait_time = 0
                avg_successful_wait = 0

            # Get tracked engines - count distinct engine types from attempts
            tracked_engines_query = session.query(
                func.count(func.distinct(RateLimitAttempt.engine_type))
            )
            if cutoff_time > 0:
                tracked_engines_query = tracked_engines_query.filter(
                    RateLimitAttempt.timestamp >= cutoff_time
                )
            tracked_engines = tracked_engines_query.scalar() or 0

            # Get engine-specific stats from attempts
            engine_stats = []

            # Get distinct engine types from attempts
            engine_types_query = session.query(
                RateLimitAttempt.engine_type
            ).distinct()
            if cutoff_time > 0:
                engine_types_query = engine_types_query.filter(
                    RateLimitAttempt.timestamp >= cutoff_time
                )
            engine_types = [row.engine_type for row in engine_types_query.all()]

            # Preload estimates for relevant engines to avoid N+1 queries
            estimates_by_engine = {}
            if engine_types:
                all_estimates = (
                    session.query(RateLimitEstimate)
                    .filter(RateLimitEstimate.engine_type.in_(engine_types))
                    .all()
                )
                estimates_by_engine = {e.engine_type: e for e in all_estimates}

            for engine_type in engine_types:
                engine_attempts_list = [
                    a for a in attempts if a.engine_type == engine_type
                ]
                engine_attempts = len(engine_attempts_list)
                engine_success = len(
                    [a for a in engine_attempts_list if a.success]
                )

                # Get estimate from preloaded dict
                estimate = estimates_by_engine.get(engine_type)

                # Calculate recent success rate
                recent_success_rate = (
                    (engine_success / engine_attempts * 100)
                    if engine_attempts > 0
                    else 0
                )

                # Determine status based on success rate
                if estimate:
                    status = (
                        "healthy"
                        if estimate.success_rate > 0.8
                        else "degraded"
                        if estimate.success_rate > 0.5
                        else "poor"
                    )
                else:
                    status = (
                        "healthy"
                        if recent_success_rate > 80
                        else "degraded"
                        if recent_success_rate > 50
                        else "poor"
                    )

                engine_stat = {
                    "engine": engine_type,
                    "base_wait": estimate.base_wait_seconds
                    if estimate
                    else 0.0,
                    "base_wait_seconds": round(
                        estimate.base_wait_seconds if estimate else 0.0, 2
                    ),
                    "min_wait_seconds": round(
                        estimate.min_wait_seconds if estimate else 0.0, 2
                    ),
                    "max_wait_seconds": round(
                        estimate.max_wait_seconds if estimate else 0.0, 2
                    ),
                    "success_rate": round(estimate.success_rate * 100, 1)
                    if estimate
                    else recent_success_rate,
                    "total_attempts": estimate.total_attempts
                    if estimate
                    else engine_attempts,
                    "recent_attempts": engine_attempts,
                    "recent_success_rate": round(recent_success_rate, 1),
                    "attempts": engine_attempts,
                    "status": status,
                }

                if estimate:
                    from datetime import datetime

                    engine_stat["last_updated"] = datetime.fromtimestamp(
                        estimate.last_updated, UTC
                    ).isoformat()  # ISO format already includes timezone
                else:
                    engine_stat["last_updated"] = "Never"

                engine_stats.append(engine_stat)

            logger.info(
                f"Tracked engines: {tracked_engines}, engine_stats: {engine_stats}"
            )

            result = {
                "rate_limiting": {
                    "total_attempts": total_attempts,
                    "successful_attempts": successful_attempts,
                    "failed_attempts": failed_attempts,
                    "success_rate": (successful_attempts / total_attempts * 100)
                    if total_attempts > 0
                    else 0,
                    "rate_limit_events": rate_limit_events,
                    "avg_wait_time": round(float(avg_wait_time), 2),
                    "avg_successful_wait": round(float(avg_successful_wait), 2),
                    "tracked_engines": tracked_engines,
                    "engine_stats": engine_stats,
                    "total_engines_tracked": tracked_engines,
                    "healthy_engines": len(
                        [s for s in engine_stats if s["status"] == "healthy"]
                    ),
                    "degraded_engines": len(
                        [s for s in engine_stats if s["status"] == "degraded"]
                    ),
                    "poor_engines": len(
                        [s for s in engine_stats if s["status"] == "poor"]
                    ),
                }
            }

            logger.info(
                f"DEBUG: Returning rate_limiting_analytics result: {result}"
            )
            return result

    except Exception:
        logger.exception("Error getting rate limiting analytics")
        return {
            "rate_limiting": {
                "total_attempts": 0,
                "successful_attempts": 0,
                "failed_attempts": 0,
                "success_rate": 0,
                "rate_limit_events": 0,
                "avg_wait_time": 0,
                "avg_successful_wait": 0,
                "tracked_engines": 0,
                "engine_stats": [],
                "total_engines_tracked": 0,
                "healthy_engines": 0,
                "degraded_engines": 0,
                "poor_engines": 0,
                "error": "An internal error occurred while processing the request.",
            }
        }


@metrics_bp.route("/")
@login_required
def metrics_dashboard():
    """Render the metrics dashboard page."""
    return render_template_with_defaults("pages/metrics.html")


@metrics_bp.route("/context-overflow")
@login_required
def context_overflow_page():
    """Context overflow analytics page."""
    return render_template_with_defaults("pages/context_overflow.html")


@metrics_bp.route("/api/metrics")
@login_required
def api_metrics():
    """Get overall metrics data."""
    logger.debug("api_metrics endpoint called")
    try:
        # Get username from session
        username = flask_session["username"]

        # Get time period and research mode from query parameters
        period = request.args.get("period", "30d")
        research_mode = request.args.get("mode", "all")

        token_counter = TokenCounter()
        search_tracker = get_search_tracker()

        # Get both token and search metrics
        token_metrics = token_counter.get_overall_metrics(
            period=period, research_mode=research_mode
        )
        search_metrics = search_tracker.get_search_metrics(
            period=period,
            research_mode=research_mode,
            username=username,
        )

        # Get user satisfaction rating data
        try:
            with get_user_db_session(username) as session:
                # Build base query with time filter
                ratings_query = session.query(ResearchRating)
                time_condition = get_time_filter_condition(
                    period, ResearchRating.created_at
                )
                if time_condition is not None:
                    ratings_query = ratings_query.filter(time_condition)

                # Get average rating
                avg_rating = ratings_query.with_entities(
                    func.avg(ResearchRating.rating).label("avg_rating")
                ).scalar()

                # Get total rating count
                total_ratings = ratings_query.count()

                user_satisfaction = {
                    "avg_rating": round(avg_rating, 1) if avg_rating else None,
                    "total_ratings": total_ratings,
                }
        except Exception:
            logger.warning("Error getting user satisfaction data")
            user_satisfaction = {"avg_rating": None, "total_ratings": 0}

        # Get strategy analytics
        strategy_data = get_strategy_analytics(period, username)
        logger.debug(f"strategy_data keys: {list(strategy_data.keys())}")

        # Get rate limiting analytics
        rate_limiting_data = get_rate_limiting_analytics(period, username)
        logger.debug(f"rate_limiting_data: {rate_limiting_data}")
        logger.debug(
            f"rate_limiting_data keys: {list(rate_limiting_data.keys())}"
        )

        # Combine metrics
        combined_metrics = {
            **token_metrics,
            **search_metrics,
            **strategy_data,
            **rate_limiting_data,
            "user_satisfaction": user_satisfaction,
        }

        logger.debug(f"combined_metrics keys: {list(combined_metrics.keys())}")
        logger.debug(
            f"combined_metrics['rate_limiting']: {combined_metrics.get('rate_limiting', 'NOT FOUND')}"
        )

        return jsonify(
            {
                "status": "success",
                "metrics": combined_metrics,
                "period": period,
                "research_mode": research_mode,
            }
        )
    except Exception:
        logger.exception("Error getting metrics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/rate-limiting")
@login_required
def api_rate_limiting_metrics():
    """Get detailed rate limiting metrics."""
    logger.info("DEBUG: api_rate_limiting_metrics endpoint called")
    try:
        username = flask_session["username"]
        period = request.args.get("period", "30d")
        rate_limiting_data = get_rate_limiting_analytics(period, username)

        return jsonify(
            {"status": "success", "data": rate_limiting_data, "period": period}
        )
    except Exception:
        logger.exception("Error getting rate limiting metrics")
        return jsonify(
            {
                "status": "error",
                "message": "Failed to retrieve rate limiting metrics",
            }
        ), 500


@metrics_bp.route("/api/rate-limiting/current")
@login_required
def api_current_rate_limits():
    """Get current rate limit estimates for all engines."""
    try:
        tracker = get_tracker()
        stats = tracker.get_stats()

        current_limits = []
        for stat in stats:
            (
                engine_type,
                base_wait,
                min_wait,
                max_wait,
                last_updated,
                total_attempts,
                success_rate,
            ) = stat
            current_limits.append(
                {
                    "engine_type": engine_type,
                    "base_wait_seconds": round(base_wait, 2),
                    "min_wait_seconds": round(min_wait, 2),
                    "max_wait_seconds": round(max_wait, 2),
                    "success_rate": round(success_rate * 100, 1),
                    "total_attempts": total_attempts,
                    "last_updated": datetime.fromtimestamp(
                        last_updated, UTC
                    ).isoformat(),  # ISO format already includes timezone
                    "status": "healthy"
                    if success_rate > 0.8
                    else "degraded"
                    if success_rate > 0.5
                    else "poor",
                }
            )

        return jsonify(
            {
                "status": "success",
                "current_limits": current_limits,
                "timestamp": datetime.now(UTC).isoformat(),
            }
        )
    except Exception:
        logger.exception("Error getting current rate limits")
        return jsonify(
            {
                "status": "error",
                "message": "Failed to retrieve current rate limits",
            }
        ), 500


@metrics_bp.route("/api/metrics/research/<string:research_id>/links")
@login_required
def api_research_link_metrics(research_id):
    """Get link analytics for a specific research."""
    try:
        username = flask_session["username"]

        with get_user_db_session(username) as session:
            # Get all resources for this specific research
            resources = (
                session.query(ResearchResource)
                .filter(ResearchResource.research_id == research_id)
                .all()
            )

            if not resources:
                return jsonify(
                    {
                        "status": "success",
                        "data": {
                            "total_links": 0,
                            "unique_domains": 0,
                            "domains": [],
                            "category_distribution": {},
                            "domain_categories": {},
                            "resources": [],
                        },
                    }
                )

            # Extract domain information
            domain_counts: dict[str, Any] = {}

            # Generic category counting from LLM classifications
            category_counts: dict[str, Any] = {}

            # First pass: collect all domains
            all_domains = set()
            for resource in resources:
                if resource.url:
                    domain = _extract_domain(resource.url)
                    if domain:
                        all_domains.add(domain)

            # Batch load all domain classifications in one query (fix N+1)
            domain_classifications_map = {}
            if all_domains:
                all_classifications = (
                    session.query(DomainClassification)
                    .filter(DomainClassification.domain.in_(all_domains))
                    .all()
                )
                for classification in all_classifications:
                    domain_classifications_map[classification.domain] = (
                        classification
                    )

            # Second pass: process resources with pre-loaded classifications
            for resource in resources:
                if resource.url:
                    try:
                        domain = _extract_domain(resource.url)
                        if not domain:
                            continue

                        domain_counts[domain] = domain_counts.get(domain, 0) + 1

                        # Count categories from pre-loaded classifications (no N+1)
                        classification = domain_classifications_map.get(domain)
                        if classification:
                            category = classification.category
                            category_counts[category] = (
                                category_counts.get(category, 0) + 1
                            )
                        else:
                            category_counts["Unclassified"] = (
                                category_counts.get("Unclassified", 0) + 1
                            )
                    except (AttributeError, KeyError) as e:
                        logger.debug(f"Error classifying domain {domain}: {e}")

            # Sort domains by count
            sorted_domains = sorted(
                domain_counts.items(), key=lambda x: x[1], reverse=True
            )

            return jsonify(
                {
                    "status": "success",
                    "data": {
                        "total_links": len(resources),
                        "unique_domains": len(domain_counts),
                        "domains": [
                            {
                                "domain": domain,
                                "count": count,
                                "percentage": round(
                                    count / len(resources) * 100, 1
                                ),
                            }
                            for domain, count in sorted_domains[
                                :20
                            ]  # Top 20 domains
                        ],
                        "category_distribution": category_counts,
                        "domain_categories": category_counts,  # Generic categories from LLM
                        "resources": [
                            {
                                "title": r.title or "Untitled",
                                "url": r.url,
                                "preview": r.content_preview[:200]
                                if r.content_preview
                                else None,
                            }
                            for r in resources[:10]  # First 10 resources
                        ],
                    },
                }
            )

    except Exception:
        logger.exception("Error getting research link metrics")
        return jsonify(
            {"status": "error", "message": "Failed to retrieve link metrics"}
        ), 500


@metrics_bp.route("/api/metrics/research/<string:research_id>")
@login_required
def api_research_metrics(research_id):
    """Get metrics for a specific research."""
    try:
        token_counter = TokenCounter()
        metrics = token_counter.get_research_metrics(research_id)
        return jsonify({"status": "success", "metrics": metrics})
    except Exception:
        logger.exception("Error getting research metrics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/metrics/research/<string:research_id>/timeline")
@login_required
def api_research_timeline_metrics(research_id):
    """Get timeline metrics for a specific research."""
    try:
        token_counter = TokenCounter()
        timeline_metrics = token_counter.get_research_timeline_metrics(
            research_id
        )
        return jsonify({"status": "success", "metrics": timeline_metrics})
    except Exception:
        logger.exception("Error getting research timeline metrics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/metrics/research/<string:research_id>/search")
@login_required
def api_research_search_metrics(research_id):
    """Get search metrics for a specific research."""
    try:
        username = flask_session["username"]
        search_tracker = get_search_tracker()
        search_metrics = search_tracker.get_research_search_metrics(
            research_id, username=username
        )
        return jsonify({"status": "success", "metrics": search_metrics})
    except Exception:
        logger.exception("Error getting research search metrics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/metrics/enhanced")
@login_required
def api_enhanced_metrics():
    """Get enhanced Phase 1 tracking metrics."""
    try:
        # Get time period and research mode from query parameters
        period = request.args.get("period", "30d")
        research_mode = request.args.get("mode", "all")
        username = flask_session["username"]

        token_counter = TokenCounter()
        search_tracker = get_search_tracker()

        enhanced_metrics = token_counter.get_enhanced_metrics(
            period=period, research_mode=research_mode
        )

        # Add search time series data for the chart
        search_time_series = search_tracker.get_search_time_series(
            period=period,
            research_mode=research_mode,
            username=username,
        )
        enhanced_metrics["search_time_series"] = search_time_series

        # Add rating analytics
        rating_analytics = get_rating_analytics(period, research_mode, username)
        enhanced_metrics.update(rating_analytics)

        return jsonify(
            {
                "status": "success",
                "metrics": enhanced_metrics,
                "period": period,
                "research_mode": research_mode,
            }
        )
    except Exception:
        logger.exception("Error getting enhanced metrics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/ratings/<string:research_id>", methods=["GET"])
@login_required
def api_get_research_rating(research_id):
    """Get rating for a specific research session."""
    try:
        username = flask_session["username"]

        with get_user_db_session(username) as session:
            rating = (
                session.query(ResearchRating)
                .filter_by(research_id=research_id)
                .first()
            )

            if rating:
                return jsonify(
                    {
                        "status": "success",
                        "rating": rating.rating,
                        "created_at": rating.created_at.isoformat(),
                        "updated_at": rating.updated_at.isoformat(),
                    }
                )
            return jsonify({"status": "success", "rating": None})

    except Exception:
        logger.exception("Error getting research rating")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/ratings/<string:research_id>", methods=["POST"])
@login_required
@require_json_body(error_format="status")
def api_save_research_rating(research_id):
    """Save or update rating for a specific research session."""
    try:
        username = flask_session["username"]

        data = request.get_json()
        rating_value = data.get("rating")

        if (
            not rating_value
            or not isinstance(rating_value, int)
            or rating_value < 1
            or rating_value > 5
        ):
            return (
                jsonify(
                    {
                        "status": "error",
                        "message": "Rating must be an integer between 1 and 5",
                    }
                ),
                400,
            )

        with get_user_db_session(username) as session:
            # Check if rating already exists
            existing_rating = (
                session.query(ResearchRating)
                .filter_by(research_id=research_id)
                .first()
            )

            if existing_rating:
                # Update existing rating
                existing_rating.rating = rating_value
                existing_rating.updated_at = func.now()
            else:
                # Create new rating
                new_rating = ResearchRating(
                    research_id=research_id, rating=rating_value
                )
                session.add(new_rating)

            session.commit()

            return jsonify(
                {
                    "status": "success",
                    "message": "Rating saved successfully",
                    "rating": rating_value,
                }
            )

    except Exception:
        logger.exception("Error saving research rating")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/star-reviews")
@login_required
def star_reviews():
    """Display star reviews metrics page."""
    return render_template_with_defaults("pages/star_reviews.html")


@metrics_bp.route("/costs")
@login_required
def cost_analytics():
    """Display cost analytics page."""
    return render_template_with_defaults("pages/cost_analytics.html")


@metrics_bp.route("/api/star-reviews")
@login_required
def api_star_reviews():
    """Get star reviews analytics data."""
    try:
        username = flask_session["username"]

        period = request.args.get("period", "30d")

        with get_user_db_session(username) as session:
            # Build base query with time filter
            base_query = session.query(ResearchRating)
            time_condition = get_time_filter_condition(
                period, ResearchRating.created_at
            )
            if time_condition is not None:
                base_query = base_query.filter(time_condition)

            # Overall rating statistics
            overall_stats = session.query(
                func.avg(ResearchRating.rating).label("avg_rating"),
                func.count(ResearchRating.rating).label("total_ratings"),
                func.sum(case((ResearchRating.rating == 5, 1), else_=0)).label(
                    "five_star"
                ),
                func.sum(case((ResearchRating.rating == 4, 1), else_=0)).label(
                    "four_star"
                ),
                func.sum(case((ResearchRating.rating == 3, 1), else_=0)).label(
                    "three_star"
                ),
                func.sum(case((ResearchRating.rating == 2, 1), else_=0)).label(
                    "two_star"
                ),
                func.sum(case((ResearchRating.rating == 1, 1), else_=0)).label(
                    "one_star"
                ),
            )

            if time_condition is not None:
                overall_stats = overall_stats.filter(time_condition)

            overall_stats = overall_stats.first()

            # Ratings by LLM model (get from token_usage since Research doesn't have model field)
            llm_ratings_query = session.query(
                func.coalesce(TokenUsage.model_name, "Unknown").label("model"),
                func.avg(ResearchRating.rating).label("avg_rating"),
                func.count(ResearchRating.rating).label("rating_count"),
                func.sum(case((ResearchRating.rating >= 4, 1), else_=0)).label(
                    "positive_ratings"
                ),
            ).outerjoin(
                TokenUsage, ResearchRating.research_id == TokenUsage.research_id
            )

            if time_condition is not None:
                llm_ratings_query = llm_ratings_query.filter(time_condition)

            llm_ratings = (
                llm_ratings_query.group_by(TokenUsage.model_name)
                .order_by(func.avg(ResearchRating.rating).desc())
                .all()
            )

            # Ratings by search engine (join with token_usage to get search engine info)
            search_engine_ratings_query = session.query(
                func.coalesce(
                    TokenUsage.search_engine_selected, "Unknown"
                ).label("search_engine"),
                func.avg(ResearchRating.rating).label("avg_rating"),
                func.count(ResearchRating.rating).label("rating_count"),
                func.sum(case((ResearchRating.rating >= 4, 1), else_=0)).label(
                    "positive_ratings"
                ),
            ).outerjoin(
                TokenUsage, ResearchRating.research_id == TokenUsage.research_id
            )

            if time_condition is not None:
                search_engine_ratings_query = (
                    search_engine_ratings_query.filter(time_condition)
                )

            search_engine_ratings = (
                search_engine_ratings_query.group_by(
                    TokenUsage.search_engine_selected
                )
                .having(func.count(ResearchRating.rating) > 0)
                .order_by(func.avg(ResearchRating.rating).desc())
                .all()
            )

            # Rating trends over time
            rating_trends_query = session.query(
                func.date(ResearchRating.created_at).label("date"),
                func.avg(ResearchRating.rating).label("avg_rating"),
                func.count(ResearchRating.rating).label("daily_count"),
            )

            if time_condition is not None:
                rating_trends_query = rating_trends_query.filter(time_condition)

            rating_trends = (
                rating_trends_query.group_by(
                    func.date(ResearchRating.created_at)
                )
                .order_by("date")
                .all()
            )

            # Recent ratings with research details
            recent_ratings_query = (
                session.query(
                    ResearchRating.rating,
                    ResearchRating.created_at,
                    ResearchRating.research_id,
                    Research.query,
                    Research.mode,
                    TokenUsage.model_name,
                    Research.created_at,
                )
                .outerjoin(Research, ResearchRating.research_id == Research.id)
                .outerjoin(
                    TokenUsage,
                    ResearchRating.research_id == TokenUsage.research_id,
                )
            )

            if time_condition is not None:
                recent_ratings_query = recent_ratings_query.filter(
                    time_condition
                )

            recent_ratings = (
                recent_ratings_query.order_by(ResearchRating.created_at.desc())
                .limit(20)
                .all()
            )

            return jsonify(
                {
                    "overall_stats": {
                        "avg_rating": round(overall_stats.avg_rating or 0, 2),
                        "total_ratings": overall_stats.total_ratings or 0,
                        "rating_distribution": {
                            "5": overall_stats.five_star or 0,
                            "4": overall_stats.four_star or 0,
                            "3": overall_stats.three_star or 0,
                            "2": overall_stats.two_star or 0,
                            "1": overall_stats.one_star or 0,
                        },
                    },
                    "llm_ratings": [
                        {
                            "model": rating.model,
                            "avg_rating": round(rating.avg_rating or 0, 2),
                            "rating_count": rating.rating_count or 0,
                            "positive_ratings": rating.positive_ratings or 0,
                            "satisfaction_rate": round(
                                (rating.positive_ratings or 0)
                                / max(rating.rating_count or 1, 1)
                                * 100,
                                1,
                            ),
                        }
                        for rating in llm_ratings
                    ],
                    "search_engine_ratings": [
                        {
                            "search_engine": rating.search_engine,
                            "avg_rating": round(rating.avg_rating or 0, 2),
                            "rating_count": rating.rating_count or 0,
                            "positive_ratings": rating.positive_ratings or 0,
                            "satisfaction_rate": round(
                                (rating.positive_ratings or 0)
                                / max(rating.rating_count or 1, 1)
                                * 100,
                                1,
                            ),
                        }
                        for rating in search_engine_ratings
                    ],
                    "rating_trends": [
                        {
                            "date": str(trend.date),
                            "avg_rating": round(trend.avg_rating or 0, 2),
                            "count": trend.daily_count or 0,
                        }
                        for trend in rating_trends
                    ],
                    "recent_ratings": [
                        {
                            "rating": rating.rating,
                            "created_at": str(rating.created_at),
                            "research_id": rating.research_id,
                            "query": (
                                rating.query
                                if rating.query
                                else f"Research Session #{rating.research_id}"
                            ),
                            "mode": rating.mode
                            if rating.mode
                            else "Standard Research",
                            "llm_model": (
                                rating.model_name
                                if rating.model_name
                                else "LLM Model"
                            ),
                        }
                        for rating in recent_ratings
                    ],
                }
            )

    except Exception:
        logger.exception("Error getting star reviews data")
        return (
            jsonify(
                {"error": "An internal error occurred. Please try again later."}
            ),
            500,
        )


@metrics_bp.route("/api/pricing")
@login_required
def api_pricing():
    """Get current LLM pricing data."""
    try:
        from ...metrics.pricing.pricing_fetcher import PricingFetcher

        # Use static pricing data instead of async
        fetcher = PricingFetcher()
        pricing_data = fetcher.static_pricing

        return jsonify(
            {
                "status": "success",
                "pricing": pricing_data,
                "last_updated": datetime.now(UTC).isoformat(),
                "note": "Pricing data is from static configuration. Real-time APIs not available for most providers.",
            }
        )

    except Exception:
        logger.exception("Error fetching pricing data")
        return jsonify({"error": "Internal Server Error"}), 500


@metrics_bp.route("/api/pricing/<model_name>")
@login_required
def api_model_pricing(model_name):
    """Get pricing for a specific model."""
    try:
        # Optional provider parameter
        provider = request.args.get("provider")

        from ...metrics.pricing.cost_calculator import CostCalculator

        # Use synchronous approach with cached/static pricing
        calculator = CostCalculator()
        pricing = calculator.cache.get_model_pricing(
            model_name
        ) or calculator.calculate_cost_sync(model_name, 1000, 1000).get(
            "pricing_used", {}
        )

        return jsonify(
            {
                "status": "success",
                "model": model_name,
                "provider": provider,
                "pricing": pricing,
                "last_updated": datetime.now(UTC).isoformat(),
            }
        )

    except Exception:
        logger.exception(f"Error getting pricing for model: {model_name}")
        return jsonify({"error": "An internal error occurred"}), 500


@metrics_bp.route("/api/cost-calculation", methods=["POST"])
@login_required
@require_json_body(error_message="No data provided")
def api_cost_calculation():
    """Calculate cost for token usage."""
    try:
        data = request.get_json()
        model_name = data.get("model_name")
        provider = data.get("provider")  # Optional provider parameter
        prompt_tokens = data.get("prompt_tokens", 0)
        completion_tokens = data.get("completion_tokens", 0)

        if not model_name:
            return jsonify({"error": "model_name is required"}), 400

        from ...metrics.pricing.cost_calculator import CostCalculator

        # Use synchronous cost calculation
        calculator = CostCalculator()
        cost_data = calculator.calculate_cost_sync(
            model_name, prompt_tokens, completion_tokens
        )

        return jsonify(
            {
                "status": "success",
                "model_name": model_name,
                "provider": provider,
                "prompt_tokens": prompt_tokens,
                "completion_tokens": completion_tokens,
                "total_tokens": prompt_tokens + completion_tokens,
                **cost_data,
            }
        )

    except Exception:
        logger.exception("Error calculating cost")
        return jsonify({"error": "An internal error occurred"}), 500


@metrics_bp.route("/api/research-costs/<string:research_id>")
@login_required
def api_research_costs(research_id):
    """Get cost analysis for a specific research session."""
    try:
        username = flask_session["username"]

        with get_user_db_session(username) as session:
            # Get token usage records for this research
            usage_records = (
                session.query(TokenUsage)
                .filter(TokenUsage.research_id == research_id)
                .all()
            )

            if not usage_records:
                return jsonify(
                    {
                        "status": "success",
                        "research_id": research_id,
                        "total_cost": 0.0,
                        "message": "No token usage data found for this research session",
                    }
                )

            # Convert to dict format for cost calculation
            usage_data = []
            for record in usage_records:
                usage_data.append(
                    {
                        "model_name": record.model_name,
                        "provider": getattr(
                            record, "provider", None
                        ),  # Handle both old and new records
                        "prompt_tokens": record.prompt_tokens,
                        "completion_tokens": record.completion_tokens,
                        "timestamp": record.timestamp,
                    }
                )

            from ...metrics.pricing.cost_calculator import CostCalculator

            # Use synchronous calculation for research costs
            calculator = CostCalculator()
            costs = []
            for record in usage_data:
                cost_data = calculator.calculate_cost_sync(
                    record["model_name"],
                    record["prompt_tokens"],
                    record["completion_tokens"],
                )
                costs.append({**record, **cost_data})

            total_cost = sum(c["total_cost"] for c in costs)
            total_prompt_tokens = sum(r["prompt_tokens"] for r in usage_data)
            total_completion_tokens = sum(
                r["completion_tokens"] for r in usage_data
            )

            cost_summary = {
                "total_cost": round(total_cost, 6),
                "total_tokens": total_prompt_tokens + total_completion_tokens,
                "prompt_tokens": total_prompt_tokens,
                "completion_tokens": total_completion_tokens,
            }

            return jsonify(
                {
                    "status": "success",
                    "research_id": research_id,
                    **cost_summary,
                }
            )

    except Exception:
        logger.exception(
            f"Error getting research costs for research: {research_id}"
        )
        return jsonify({"error": "An internal error occurred"}), 500


@metrics_bp.route("/api/cost-analytics")
@login_required
def api_cost_analytics():
    """Get cost analytics across all research sessions."""
    try:
        username = flask_session["username"]

        period = request.args.get("period", "30d")

        with get_user_db_session(username) as session:
            # Get token usage for the period
            query = session.query(TokenUsage)
            time_condition = get_time_filter_condition(
                period, TokenUsage.timestamp
            )
            if time_condition is not None:
                query = query.filter(time_condition)

            # First check if we have any records to avoid expensive queries
            record_count = query.count()

            if record_count == 0:
                return jsonify(
                    {
                        "status": "success",
                        "period": period,
                        "overview": {
                            "total_cost": 0.0,
                            "total_tokens": 0,
                            "prompt_tokens": 0,
                            "completion_tokens": 0,
                        },
                        "top_expensive_research": [],
                        "research_count": 0,
                        "message": "No token usage data found for this period",
                    }
                )

            # If we have too many records, limit to recent ones to avoid timeout
            if record_count > 1000:
                logger.warning(
                    f"Large dataset detected ({record_count} records), limiting to recent 1000 for performance"
                )
                usage_records = (
                    query.order_by(TokenUsage.timestamp.desc())
                    .limit(1000)
                    .all()
                )
            else:
                usage_records = query.all()

            # Convert to dict format
            usage_data = []
            for record in usage_records:
                usage_data.append(
                    {
                        "model_name": record.model_name,
                        "provider": getattr(
                            record, "provider", None
                        ),  # Handle both old and new records
                        "prompt_tokens": record.prompt_tokens,
                        "completion_tokens": record.completion_tokens,
                        "research_id": record.research_id,
                        "timestamp": record.timestamp,
                    }
                )

            from ...metrics.pricing.cost_calculator import CostCalculator

            # Use synchronous calculation
            calculator = CostCalculator()

            # Calculate overall costs
            costs = []
            for record in usage_data:
                cost_data = calculator.calculate_cost_sync(
                    record["model_name"],
                    record["prompt_tokens"],
                    record["completion_tokens"],
                )
                costs.append({**record, **cost_data})

            total_cost = sum(c["total_cost"] for c in costs)
            total_prompt_tokens = sum(r["prompt_tokens"] for r in usage_data)
            total_completion_tokens = sum(
                r["completion_tokens"] for r in usage_data
            )

            cost_summary = {
                "total_cost": round(total_cost, 6),
                "total_tokens": total_prompt_tokens + total_completion_tokens,
                "prompt_tokens": total_prompt_tokens,
                "completion_tokens": total_completion_tokens,
            }

            # Group by research_id for per-research costs
            research_costs: dict[str, Any] = {}
            for record in usage_data:
                rid = record["research_id"]
                if rid not in research_costs:
                    research_costs[rid] = []
                research_costs[rid].append(record)

            # Calculate cost per research
            research_summaries = {}
            for rid, records in research_costs.items():
                research_total: float = 0
                for record in records:
                    cost_data = calculator.calculate_cost_sync(
                        record["model_name"],
                        record["prompt_tokens"],
                        record["completion_tokens"],
                    )
                    research_total += cost_data["total_cost"]
                research_summaries[rid] = {
                    "total_cost": round(research_total, 6)
                }

            # Top expensive research sessions
            top_expensive = sorted(
                [
                    (rid, data["total_cost"])
                    for rid, data in research_summaries.items()
                ],
                key=lambda x: x[1],
                reverse=True,
            )[:10]

            return jsonify(
                {
                    "status": "success",
                    "period": period,
                    "overview": cost_summary,
                    "top_expensive_research": [
                        {"research_id": rid, "total_cost": cost}
                        for rid, cost in top_expensive
                    ],
                    "research_count": len(research_summaries),
                }
            )

    except Exception:
        logger.exception("Error getting cost analytics")
        # Return a more graceful error response
        return (
            jsonify(
                {
                    "status": "success",
                    "period": period,
                    "overview": {
                        "total_cost": 0.0,
                        "total_tokens": 0,
                        "prompt_tokens": 0,
                        "completion_tokens": 0,
                    },
                    "top_expensive_research": [],
                    "research_count": 0,
                    "error": "Cost analytics temporarily unavailable",
                }
            ),
            200,
        )  # Return 200 to avoid breaking the UI


@metrics_bp.route("/links")
@login_required
def link_analytics():
    """Display link analytics page."""
    return render_template_with_defaults("pages/link_analytics.html")


@metrics_bp.route("/api/link-analytics")
@login_required
def api_link_analytics():
    """Get link analytics data."""
    try:
        username = flask_session["username"]

        period = request.args.get("period", "30d")

        # Get link analytics data
        link_data = get_link_analytics(period, username)

        return jsonify(
            {
                "status": "success",
                "data": link_data["link_analytics"],
                "period": period,
            }
        )

    except Exception:
        logger.exception("Error getting link analytics")
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "An internal error occurred. Please try again later.",
                }
            ),
            500,
        )


@metrics_bp.route("/api/domain-classifications", methods=["GET"])
@login_required
def api_get_domain_classifications():
    """Get all domain classifications."""
    classifier = None
    try:
        username = flask_session["username"]

        classifier = DomainClassifier(username)
        classifications = classifier.get_all_classifications()

        return jsonify(
            {
                "status": "success",
                "classifications": [c.to_dict() for c in classifications],
                "total": len(classifications),
            }
        )

    except Exception:
        logger.exception("Error getting domain classifications")
        return jsonify(
            {"status": "error", "message": "Failed to retrieve classifications"}
        ), 500
    finally:
        if classifier is not None:
            from ...utilities.resource_utils import safe_close

            safe_close(classifier, "domain classifier")


@metrics_bp.route("/api/domain-classifications/summary", methods=["GET"])
@login_required
def api_get_classifications_summary():
    """Get summary of domain classifications by category."""
    classifier = None
    try:
        username = flask_session["username"]

        classifier = DomainClassifier(username)
        summary = classifier.get_categories_summary()

        return jsonify({"status": "success", "summary": summary})

    except Exception:
        logger.exception("Error getting classifications summary")
        return jsonify(
            {"status": "error", "message": "Failed to retrieve summary"}
        ), 500
    finally:
        if classifier is not None:
            from ...utilities.resource_utils import safe_close

            safe_close(classifier, "domain classifier")


@metrics_bp.route("/api/domain-classifications/classify", methods=["POST"])
@login_required
def api_classify_domains():
    """Trigger classification of a specific domain or batch classification."""
    classifier = None
    try:
        username = flask_session["username"]

        data = request.get_json() or {}
        domain = data.get("domain")
        force_update = data.get("force_update", False)
        batch_mode = data.get("batch", False)

        # Get settings snapshot for LLM configuration
        from ...settings.manager import SettingsManager
        from ...database.session_context import get_user_db_session

        with get_user_db_session(username) as db_session:
            settings_manager = SettingsManager(db_session=db_session)
            settings_snapshot = settings_manager.get_all_settings()

        classifier = DomainClassifier(
            username, settings_snapshot=settings_snapshot
        )

        if domain and not batch_mode:
            # Classify single domain
            logger.info(f"Classifying single domain: {domain}")
            classification = classifier.classify_domain(domain, force_update)
            if classification:
                return jsonify(
                    {
                        "status": "success",
                        "classification": classification.to_dict(),
                    }
                )
            return jsonify(
                {
                    "status": "error",
                    "message": f"Failed to classify domain: {domain}",
                }
            ), 400
        if batch_mode:
            # Batch classification - this should really be a background task
            # For now, we'll just return immediately and let the frontend poll
            logger.info("Starting batch classification of all domains")
            results = classifier.classify_all_domains(force_update)

            return jsonify({"status": "success", "results": results})
        return jsonify(
            {
                "status": "error",
                "message": "Must provide either 'domain' or set 'batch': true",
            }
        ), 400

    except Exception:
        logger.exception("Error classifying domains")
        return jsonify(
            {"status": "error", "message": "Failed to classify domains"}
        ), 500
    finally:
        if classifier is not None:
            from ...utilities.resource_utils import safe_close

            safe_close(classifier, "domain classifier")


@metrics_bp.route("/api/domain-classifications/progress", methods=["GET"])
@login_required
def api_classification_progress():
    """Get progress of domain classification task."""
    try:
        username = flask_session["username"]

        # Get counts of classified vs unclassified domains
        with get_user_db_session(username) as session:
            # Count total unique domains
            resources = session.query(ResearchResource.url).distinct().all()
            domains = set()

            for (url,) in resources:
                if url:
                    domain = _extract_domain(url)
                    if domain:
                        domains.add(domain)

            all_domains = sorted(domains)
            total_domains = len(domains)

            # Count classified domains
            classified_count = session.query(DomainClassification).count()

            return jsonify(
                {
                    "status": "success",
                    "progress": {
                        "total_domains": total_domains,
                        "classified": classified_count,
                        "unclassified": total_domains - classified_count,
                        "percentage": round(
                            (classified_count / total_domains * 100)
                            if total_domains > 0
                            else 0,
                            1,
                        ),
                        "all_domains": all_domains,  # Return all domains for classification
                    },
                }
            )

    except Exception:
        logger.exception("Error getting classification progress")
        return jsonify(
            {"status": "error", "message": "Failed to retrieve progress"}
        ), 500
