import io
import json
import platform
import subprocess
from datetime import datetime, UTC
from pathlib import Path

from flask import (
    Blueprint,
    g,
    jsonify,
    redirect,
    request,
    send_file,
    session,
    url_for,
)
from loguru import logger
from ...settings.logger import log_settings
from sqlalchemy import func

# Security imports
from ...security import FileUploadValidator, upload_rate_limit

# Services imports
from ..services.pdf_extraction_service import get_pdf_extraction_service

from ...database.models import (
    QueuedResearch,
    ResearchHistory,
    ResearchLog,
    UserActiveResearch,
)
from ...database.models.library import Document as Document
from ...database.session_context import get_user_db_session
from ..auth.decorators import login_required
from ..models.database import calculate_duration
from ..services.research_service import (
    export_report_to_memory,
    run_research_process,
    start_research_process,
)
from ..utils.rate_limiter import limiter
from ..utils.templates import render_template_with_defaults
from .globals import active_research, termination_flags

# Create a Blueprint for the research application
research_bp = Blueprint("research", __name__)


# Add static route at the root level
@research_bp.route("/redirect-static/<path:path>")
def redirect_static(path):
    """Redirect old static URLs to new static URLs"""
    return redirect(url_for("static", filename=path))


@research_bp.route("/progress/<string:research_id>")
@login_required
def progress_page(research_id):
    """Render the research progress page"""
    return render_template_with_defaults("pages/progress.html")


@research_bp.route("/details/<string:research_id>")
@login_required
def research_details_page(research_id):
    """Render the research details page"""
    return render_template_with_defaults("pages/details.html")


@research_bp.route("/results/<string:research_id>")
@login_required
def results_page(research_id):
    """Render the research results page"""
    return render_template_with_defaults("pages/results.html")


@research_bp.route("/history")
@login_required
def history_page():
    """Render the history page"""
    return render_template_with_defaults("pages/history.html")


# Add missing settings routes
@research_bp.route("/settings", methods=["GET"])
@login_required
def settings_page():
    """Render the settings page"""
    return render_template_with_defaults("settings_dashboard.html")


@research_bp.route("/settings/main", methods=["GET"])
@login_required
def main_config_page():
    """Render the main settings config page"""
    return render_template_with_defaults("main_config.html")


@research_bp.route("/settings/collections", methods=["GET"])
@login_required
def collections_config_page():
    """Render the collections config page"""
    return render_template_with_defaults("collections_config.html")


@research_bp.route("/settings/api_keys", methods=["GET"])
@login_required
def api_keys_config_page():
    """Render the API keys config page"""
    return render_template_with_defaults("api_keys_config.html")


@research_bp.route("/settings/search_engines", methods=["GET"])
@login_required
def search_engines_config_page():
    """Render the search engines config page"""
    return render_template_with_defaults("search_engines_config.html")


@research_bp.route("/settings/llm", methods=["GET"])
@login_required
def llm_config_page():
    """Render the LLM config page"""
    return render_template_with_defaults("llm_config.html")


@research_bp.route("/api/start_research", methods=["POST"])
@login_required
def start_research():
    data = request.json
    # Debug logging to trace model parameter
    logger.debug(f"Received request data: {data}")
    logger.debug(f"Request data keys: {list(data.keys()) if data else 'None'}")

    # Check if this is a news search
    metadata = data.get("metadata", {})
    if metadata.get("is_news_search"):
        logger.info(
            f"News search request received: triggered_by={metadata.get('triggered_by', 'unknown')}"
        )

    query = data.get("query")
    mode = data.get("mode", "quick")

    # Replace date placeholders if they exist
    if query and "YYYY-MM-DD" in query:
        # Use local system time
        current_date = datetime.now(UTC).strftime("%Y-%m-%d")

        original_query = query
        query = query.replace("YYYY-MM-DD", current_date)
        logger.info(
            f"Replaced date placeholder in query: {original_query[:100]}... -> {query[:100]}..."
        )
        logger.info(f"Using date: {current_date}")

        # Update metadata to track the replacement
        if not metadata:
            metadata = {}
        metadata["original_query"] = original_query
        metadata["processed_query"] = query
        metadata["date_replaced"] = current_date
        data["metadata"] = metadata

    # Get parameters from request or use database settings
    from ..services.settings_manager import SettingsManager

    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    with get_user_db_session(username) as db_session:
        settings_manager = SettingsManager(db_session=db_session)

    # Get model provider and model selections - use database settings if not provided
    model_provider = data.get("model_provider")
    if not model_provider:
        model_provider = settings_manager.get_setting("llm.provider", "OLLAMA")
        logger.debug(
            f"No model_provider in request, using database setting: {model_provider}"
        )
    else:
        logger.debug(f"Using model_provider from request: {model_provider}")

    model = data.get("model")
    if not model:
        model = settings_manager.get_setting("llm.model", None)
        logger.debug(f"No model in request, using database setting: {model}")
    else:
        logger.debug(f"Using model from request: {model}")

    custom_endpoint = data.get("custom_endpoint")
    if not custom_endpoint and model_provider == "OPENAI_ENDPOINT":
        custom_endpoint = settings_manager.get_setting(
            "llm.openai_endpoint.url", None
        )
        logger.debug(
            f"No custom_endpoint in request, using database setting: {custom_endpoint}"
        )

    # Get Ollama URL from request or settings
    ollama_url = data.get("ollama_url")
    if not ollama_url and model_provider == "OLLAMA":
        ollama_url = settings_manager.get_setting(
            "llm.ollama.url", "http://localhost:11434"
        )
        logger.debug(
            f"No ollama_url in request, using database setting: {ollama_url}"
        )

    search_engine = data.get("search_engine") or data.get("search_tool")
    if not search_engine:
        search_engine = settings_manager.get_setting("search.tool", "searxng")

    max_results = data.get("max_results")
    time_period = data.get("time_period")

    iterations = data.get("iterations")
    if iterations is None:
        iterations = settings_manager.get_setting("search.iterations", 5)

    questions_per_iteration = data.get("questions_per_iteration")
    if questions_per_iteration is None:
        questions_per_iteration = settings_manager.get_setting(
            "search.questions_per_iteration", 5
        )

    # Get strategy from request or database
    strategy = data.get("strategy")
    if not strategy:
        strategy = settings_manager.get_setting(
            "search.search_strategy", "source-based"
        )

    # Note: db_session already closed by context manager above

    # Debug logging for model parameter specifically
    logger.debug(
        f"Extracted model value: '{model}' (type: {type(model).__name__})"
    )

    # Log the selections for troubleshooting
    logger.info(
        f"Starting research with provider: {model_provider}, model: {model}, search engine: {search_engine}"
    )
    logger.info(
        f"Additional parameters: max_results={max_results}, time_period={time_period}, iterations={iterations}, questions={questions_per_iteration}, strategy={strategy}"
    )

    if not query:
        return jsonify({"status": "error", "message": "Query is required"}), 400

    # Validate required parameters based on provider
    if model_provider == "OPENAI_ENDPOINT" and not custom_endpoint:
        return (
            jsonify(
                {
                    "status": "error",
                    "message": "Custom endpoint URL is required for OpenAI endpoint provider",
                }
            ),
            400,
        )

    if not model:
        logger.error(
            f"No model specified or configured. Provider: {model_provider}"
        )
        return jsonify(
            {
                "status": "error",
                "message": "Model is required. Please configure a model in the settings.",
            }
        ), 400

    # Check if the user has too many active researches
    username = session.get("username")

    # Get max concurrent researches from settings
    from ...settings import SettingsManager

    with get_user_db_session() as db_session:
        settings_manager = SettingsManager(db_session)
        max_concurrent_researches = settings_manager.get_setting(
            "app.max_concurrent_researches", 3
        )

    # Use existing session from g to check active researches
    try:
        if hasattr(g, "db_session") and g.db_session:
            # Count active researches for this user
            active_count = (
                g.db_session.query(UserActiveResearch)
                .filter_by(username=username, status="in_progress")
                .count()
            )

            # Debug logging
            logger.info(
                f"Active research count for {username}: {active_count}/{max_concurrent_researches}"
            )

            should_queue = active_count >= max_concurrent_researches
            logger.info(f"Should queue new research: {should_queue}")
        else:
            logger.warning(
                "No database session available to check active researches"
            )
            should_queue = False
    except Exception:
        logger.exception("Failed to check active researches")
        # Default to not queueing if we can't check
        should_queue = False

    # Create a record in the database with explicit UTC timestamp
    import uuid
    import threading

    created_at = datetime.now(UTC).isoformat()
    research_id = str(uuid.uuid4())

    # Create organized research metadata with settings snapshot
    research_settings = {
        # Direct submission parameters
        "submission": {
            "model_provider": model_provider,
            "model": model,
            "custom_endpoint": custom_endpoint,
            "search_engine": search_engine,
            "max_results": max_results,
            "time_period": time_period,
            "iterations": iterations,
            "questions_per_iteration": questions_per_iteration,
            "strategy": strategy,
        },
        # System information
        "system": {
            "timestamp": created_at,
            "user": username,
            "version": "1.0",  # Track metadata version for future migrations
            "server_url": request.host_url,  # Add server URL for link generation
        },
    }

    # Add any additional metadata from request
    additional_metadata = data.get("metadata", {})
    if additional_metadata:
        research_settings.update(additional_metadata)
    # Get complete settings snapshot for this research
    try:
        from local_deep_research.settings import SettingsManager

        # Use the existing session from g (set by middleware)
        if hasattr(g, "db_session") and g.db_session:
            # Create SettingsManager with the existing session
            username = session.get("username")
            # Ensure any pending changes are committed
            try:
                g.db_session.commit()
            except Exception:
                g.db_session.rollback()
            settings_manager = SettingsManager(g.db_session)
            # Get all current settings as a snapshot (bypass cache to ensure fresh data)
            all_settings = settings_manager.get_all_settings(bypass_cache=True)

            # Add settings snapshot to metadata
            research_settings["settings_snapshot"] = all_settings
            logger.info(
                f"Captured {len(all_settings)} settings for research {research_id}"
            )
        else:
            # If no session in g, create a new one temporarily to get settings
            logger.warning(
                "No database session in g, creating temporary session for settings snapshot"
            )
            from ...database.thread_local_session import get_metrics_session

            # Get password from session or g
            password = getattr(g, "user_password", None)
            if not password:
                # Try to get from session password store
                from ...database.session_passwords import session_password_store

                session_id = session.get("session_id")
                if session_id:
                    password = session_password_store.get_session_password(
                        username, session_id
                    )

            if password:
                temp_session = get_metrics_session(username, password)
                if temp_session:
                    username = session.get("username")
                    settings_manager = SettingsManager(temp_session)
                    all_settings = settings_manager.get_all_settings(
                        bypass_cache=True
                    )
                    research_settings["settings_snapshot"] = all_settings
                    logger.info(
                        f"Captured {len(all_settings)} settings using temporary session for research {research_id}"
                    )
                else:
                    logger.error(
                        "Failed to create temporary session for settings snapshot"
                    )
                    raise Exception(
                        "Cannot create research without settings snapshot"
                    )
            else:
                logger.error(
                    "No password available to create session for settings snapshot"
                )
                raise Exception(
                    "Cannot create research without settings snapshot"
                )
    except Exception:
        logger.exception("Failed to capture settings snapshot")
        # Cannot continue without settings snapshot for thread-based research
        return jsonify(
            {
                "status": "error",
                "message": "Failed to capture settings for research. Please try again.",
            }
        ), 500

    # Use existing session from g
    username = session.get("username")
    if not username:
        return jsonify({"status": "error", "message": "Not authenticated"}), 401

    try:
        # Use existing session from g
        if hasattr(g, "db_session") and g.db_session:
            db_session = g.db_session
            # Determine initial status based on whether we need to queue
            initial_status = "queued" if should_queue else "in_progress"

            research = ResearchHistory(
                id=research_id,  # Set UUID as primary key
                query=query,
                mode=mode,
                status=initial_status,
                created_at=created_at,
                progress_log=[{"time": created_at, "progress": 0}],
                research_meta=research_settings,
            )
            db_session.add(research)
            db_session.commit()
            logger.info(
                f"Created research entry with UUID: {research_id}, status: {initial_status}"
            )

            if should_queue:
                # Add to queue instead of starting immediately
                # Get the next position in queue for this user
                max_position = (
                    db_session.query(func.max(QueuedResearch.position))
                    .filter_by(username=username)
                    .scalar()
                    or 0
                )

                queued_record = QueuedResearch(
                    username=username,
                    research_id=research_id,
                    query=query,
                    mode=mode,
                    settings_snapshot=research_settings,
                    position=max_position + 1,
                )
                db_session.add(queued_record)
                db_session.commit()
                logger.info(
                    f"Queued research {research_id} at position {max_position + 1} for user {username}"
                )

                # Notify queue processor with all parameters for potential direct execution
                from ..queue.processor_v2 import queue_processor

                # Get session ID for password access
                session_id = session.get("session_id")

                # Pass all parameters needed for direct execution
                queue_processor.notify_research_queued(
                    username,
                    research_id,
                    session_id=session_id,
                    query=query,
                    mode=mode,
                    settings_snapshot=research_settings,
                    model_provider=model_provider,
                    model=model,
                    custom_endpoint=custom_endpoint,
                    search_engine=search_engine,
                    max_results=max_results,
                    time_period=time_period,
                    iterations=iterations,
                    questions_per_iteration=questions_per_iteration,
                    strategy=strategy,
                )

                # Return queued status
                return jsonify(
                    {
                        "status": "queued",
                        "research_id": research_id,
                        "queue_position": max_position + 1,
                        "message": f"Your research has been queued. Position in queue: {max_position + 1}",
                    }
                )
            else:
                # Start immediately
                # Create active research tracking record
                import threading

                active_record = UserActiveResearch(
                    username=username,
                    research_id=research_id,
                    status="in_progress",
                    thread_id=str(threading.current_thread().ident),
                    settings_snapshot=research_settings,
                )
                db_session.add(active_record)
                db_session.commit()
                logger.info(
                    f"Created active research record for user {username}"
                )

                # Double-check the count after committing to handle race conditions
                # Use the existing session for the recheck
                try:
                    # Use the same session we already have
                    recheck_session = db_session
                    final_count = (
                        recheck_session.query(UserActiveResearch)
                        .filter_by(username=username, status="in_progress")
                        .count()
                    )
                    logger.info(
                        f"Final active count after commit: {final_count}/{max_concurrent_researches}"
                    )

                    if final_count > max_concurrent_researches:
                        # We exceeded the limit due to a race condition
                        # Remove this record and queue instead
                        logger.warning(
                            f"Race condition detected: {final_count} > {max_concurrent_researches}, moving to queue"
                        )
                        db_session.delete(active_record)
                        db_session.commit()

                        # Add to queue
                        max_position = (
                            db_session.query(func.max(QueuedResearch.position))
                            .filter_by(username=username)
                            .scalar()
                            or 0
                        )

                        queued_record = QueuedResearch(
                            username=username,
                            research_id=research_id,
                            query=query,
                            mode=mode,
                            settings_snapshot=research_settings,
                            position=max_position + 1,
                        )
                        db_session.add(queued_record)

                        # Update research status to queued
                        research.status = "queued"
                        db_session.commit()

                        # Notify queue processor for potential direct execution
                        from ..queue.processor_v2 import queue_processor

                        # Get session ID for password access
                        session_id = session.get("session_id")

                        # Pass all parameters needed for direct execution
                        queue_processor.notify_research_queued(
                            username,
                            research_id,
                            session_id=session_id,
                            query=query,
                            mode=mode,
                            settings_snapshot=research_settings,
                            model_provider=model_provider,
                            model=model,
                            custom_endpoint=custom_endpoint,
                            search_engine=search_engine,
                            max_results=max_results,
                            time_period=time_period,
                            iterations=iterations,
                            questions_per_iteration=questions_per_iteration,
                            strategy=strategy,
                        )

                        return jsonify(
                            {
                                "status": "queued",
                                "research_id": research_id,
                                "queue_position": max_position + 1,
                                "message": f"Your research has been queued due to concurrent limit. Position in queue: {max_position + 1}",
                            }
                        )
                except Exception as e:
                    logger.warning(f"Could not recheck active count: {e}")

    except Exception:
        logger.exception("Failed to create research entry")
        return jsonify(
            {"status": "error", "message": "Failed to create research entry"}
        ), 500

    # Only start the research if not queued
    if not should_queue:
        # Save the research strategy to the database before starting the thread
        try:
            from ..services.research_service import save_research_strategy

            save_research_strategy(research_id, strategy, username=username)
        except Exception as e:
            logger.warning(f"Could not save research strategy: {e}")

        # Debug logging for settings snapshot
        snapshot_data = research_settings.get("settings_snapshot", {})
        log_settings(snapshot_data, "Settings snapshot being passed to thread")
        if "search.tool" in snapshot_data:
            logger.debug(
                f"search.tool in snapshot: {snapshot_data['search.tool']}"
            )
        else:
            logger.debug("search.tool NOT in snapshot")

        # Get the user's password for metrics access in background thread
        # Try session password store first
        from ...database.session_passwords import session_password_store

        session_id = session.get("session_id")
        user_password = None

        if session_id:
            user_password = session_password_store.get_session_password(
                username, session_id
            )

        # Fallback to g.user_password (set by middleware if temp_auth was used)
        if not user_password:
            user_password = getattr(g, "user_password", None)

        # Last resort: try temp_auth_store
        if not user_password:
            from ...database.temp_auth import temp_auth_store

            auth_token = session.get("temp_auth_token")
            if auth_token:
                # Use peek_auth to avoid consuming the token
                auth_data = temp_auth_store.peek_auth(auth_token)
                if auth_data and auth_data[0] == username:
                    user_password = auth_data[1]

        if not user_password:
            logger.warning(
                f"No password available for metrics access for user {username}"
            )

        # Start the research process with the selected parameters
        research_thread = start_research_process(
            research_id,
            query,
            mode,
            active_research,
            termination_flags,
            run_research_process,
            username=username,  # Pass username to the thread
            user_password=user_password,  # Pass password for database access
            model_provider=model_provider,
            model=model,
            custom_endpoint=custom_endpoint,
            search_engine=search_engine,
            max_results=max_results,
            time_period=time_period,
            iterations=iterations,
            questions_per_iteration=questions_per_iteration,
            strategy=strategy,
            settings_snapshot=snapshot_data,  # Pass complete settings
        )

        # Update the active research record with the actual thread ID
        try:
            with get_user_db_session(username) as thread_session:
                active_record = (
                    thread_session.query(UserActiveResearch)
                    .filter_by(username=username, research_id=research_id)
                    .first()
                )
                if active_record:
                    active_record.thread_id = str(research_thread.ident)
                    thread_session.commit()
        except Exception as e:
            logger.warning(f"Could not update thread ID: {e}")

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


@research_bp.route("/api/terminate/<string:research_id>", methods=["POST"])
@login_required
def terminate_research(research_id):
    """Terminate an in-progress research process"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    # Check if the research exists and is in progress
    try:
        with get_user_db_session(username) as db_session:
            research = (
                db_session.query(ResearchHistory)
                .filter_by(id=research_id)
                .first()
            )

            if not research:
                return jsonify(
                    {"status": "error", "message": "Research not found"}
                ), 404

            status = research.status

            # If it's already completed or suspended, return success
            if status in ["completed", "suspended", "error"]:
                return jsonify(
                    {
                        "status": "success",
                        "message": f"Research already {status}",
                    }
                )

            # Check if it's in the active_research dict
            if research_id not in active_research:
                # Update the status in the database
                research.status = "suspended"
                db_session.commit()
                return jsonify(
                    {"status": "success", "message": "Research terminated"}
                )

            # Set the termination flag
            termination_flags[research_id] = True

            # Log the termination request - using UTC timestamp
            timestamp = datetime.now(UTC).isoformat()
            termination_message = "Research termination requested by user"
            current_progress = active_research[research_id]["progress"]

            # Create log entry
            log_entry = {
                "time": timestamp,
                "message": termination_message,
                "progress": current_progress,
                "metadata": {"phase": "termination"},
            }

            # Add to in-memory log
            active_research[research_id]["log"].append(log_entry)

            # Add to database log
            logger.log("MILESTONE", f"Research ended: {termination_message}")

            # Update the log in the database
            if research.progress_log:
                try:
                    if isinstance(research.progress_log, str):
                        current_log = json.loads(research.progress_log)
                    else:
                        current_log = research.progress_log
                except Exception:
                    current_log = []
            else:
                current_log = []

            current_log.append(log_entry)
            research.progress_log = current_log
            research.status = "suspended"
            db_session.commit()

            # Emit a socket event for the termination request
            try:
                event_data = {
                    "status": "suspended",  # Changed from 'terminating' to 'suspended'
                    "message": "Research was suspended by user request",
                }

                from ..services.socket_service import SocketIOService

                SocketIOService().emit_socket_event(
                    f"research_progress_{research_id}", event_data
                )

            except Exception:
                logger.exception("Socket emit error (non-critical)")

            return jsonify(
                {
                    "status": "success",
                    "message": "Research termination requested",
                }
            )
    except Exception:
        logger.exception("Error terminating research")
        return jsonify(
            {"status": "error", "message": "Failed to terminate research"}
        ), 500


@research_bp.route("/api/delete/<string:research_id>", methods=["DELETE"])
@login_required
def delete_research(research_id):
    """Delete a research record"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            research = (
                db_session.query(ResearchHistory)
                .filter_by(id=research_id)
                .first()
            )

            if not research:
                return jsonify(
                    {"status": "error", "message": "Research not found"}
                ), 404

            status = research.status
            report_path = research.report_path

            # Don't allow deleting research in progress
            if status == "in_progress" and research_id in active_research:
                return (
                    jsonify(
                        {
                            "status": "error",
                            "message": "Cannot delete research that is in progress",
                        }
                    ),
                    400,
                )

            # Delete report file if it exists
            if report_path and Path(report_path).exists():
                try:
                    Path(report_path).unlink()
                except Exception:
                    logger.exception("Error removing report file")

            # Delete the database record
            db_session.delete(research)
            db_session.commit()

            return jsonify({"status": "success"})
    except Exception:
        logger.exception("Error deleting research")
        return jsonify(
            {"status": "error", "message": "Failed to delete research"}
        ), 500


@research_bp.route("/api/clear_history", methods=["POST"])
@login_required
def clear_history():
    """Clear all research history"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            # Get all research records first to clean up files
            research_records = db_session.query(ResearchHistory).all()

            # Clean up report files
            for research in research_records:
                # Skip active research
                if research.id in active_research:
                    continue

                # Delete report file if it exists
                if research.report_path and Path(research.report_path).exists():
                    try:
                        Path(research.report_path).unlink()
                    except Exception:
                        logger.exception("Error removing report file")

            # Delete records from the database, except active research
            if active_research:
                db_session.query(ResearchHistory).filter(
                    ~ResearchHistory.id.in_(list(active_research.keys()))
                ).delete(synchronize_session=False)
            else:
                db_session.query(ResearchHistory).delete(
                    synchronize_session=False
                )

            db_session.commit()

            return jsonify({"status": "success"})
    except Exception:
        logger.exception("Error clearing history")
        return jsonify(
            {"status": "error", "message": "Failed to process request"}
        ), 500


@research_bp.route("/open_file_location", methods=["POST"])
@login_required
def open_file_location():
    """Open a file location in the system file explorer"""
    data = request.json
    file_path = data.get("path")

    if not file_path:
        return jsonify({"status": "error", "message": "Path is required"}), 400

    # Get the user's data directory as the safe root
    from ...config.paths import get_data_directory

    safe_root = Path(get_data_directory()).resolve()

    # Use centralized path validator for security
    try:
        from ...security.path_validator import PathValidator

        file_path = PathValidator.validate_data_path(file_path, str(safe_root))

    except Exception:
        logger.exception("Path validation error")
        return jsonify({"status": "error", "message": "Invalid path"}), 400

    # Check if path exists
    if not file_path.exists():
        return jsonify(
            {"status": "error", "message": "Path does not exist"}
        ), 404

    try:
        if platform.system() == "Windows":
            # On Windows, open the folder and select the file
            if file_path.is_file():
                subprocess.run(
                    ["explorer", "/select,", str(file_path)], check=True
                )
            else:
                # If it's a directory, just open it
                subprocess.run(["explorer", str(file_path)], check=True)
        elif platform.system() == "Darwin":  # macOS
            subprocess.run(["open", str(file_path)], check=True)
        else:  # Linux and others
            subprocess.run(["xdg-open", str(file_path.parent)], check=True)

        return jsonify({"status": "success"})
    except Exception:
        logger.exception("Error opening a file")
        return jsonify(
            {"status": "error", "message": "Failed to process request"}
        ), 500


@research_bp.route("/api/save_raw_config", methods=["POST"])
@login_required
def save_raw_config():
    """Save raw configuration"""
    data = request.json
    raw_config = data.get("raw_config")

    if not raw_config:
        return (
            jsonify(
                {"success": False, "error": "Raw configuration is required"}
            ),
            400,
        )

    try:
        from ...security.file_write_verifier import write_file_verified

        # Get the config file path
        config_dir = Path.home() / ".local_deep_research"
        config_dir.mkdir(parents=True, exist_ok=True)
        config_path = config_dir / "config.toml"

        # Write the configuration to file
        write_file_verified(
            config_path,
            raw_config,
            "system.allow_config_write",
            context="system configuration file",
        )

        return jsonify({"success": True})
    except Exception:
        logger.exception("Error saving configuration file")
        return jsonify(
            {"success": False, "error": "Failed to process request"}
        ), 500


@research_bp.route("/api/history", methods=["GET"])
@login_required
def get_history():
    """Get research history"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            # Query all research history ordered by created_at
            research_records = (
                db_session.query(ResearchHistory)
                .order_by(ResearchHistory.created_at.desc())
                .all()
            )

        history_items = []
        for research in research_records:
            # Calculate duration if completed
            duration_seconds = None
            if research.completed_at and research.created_at:
                try:
                    duration_seconds = calculate_duration(
                        research.created_at, research.completed_at
                    )
                except Exception:
                    logger.exception("Error calculating duration")

            # Count documents in the library for this research
            doc_count = (
                db_session.query(Document)
                .filter_by(research_id=research.id)
                .count()
            )

            # Create a history item
            item = {
                "id": research.id,
                "query": research.query,
                "mode": research.mode,
                "status": research.status,
                "created_at": research.created_at,
                "completed_at": research.completed_at,
                "duration_seconds": duration_seconds,
                "report_path": research.report_path,
                "metadata": research.research_meta,  # Include metadata for news
                "document_count": doc_count,  # Add document count
            }

            # Add title if it exists
            if hasattr(research, "title") and research.title is not None:
                item["title"] = research.title

            history_items.append(item)

        return jsonify({"status": "success", "items": history_items})
    except Exception:
        logger.exception("Error getting history")
        return jsonify(
            {"status": "error", "message": "Failed to process request"}
        ), 500


@research_bp.route("/api/research/<string:research_id>")
@login_required
def get_research_details(research_id):
    """Get full details of a research using ORM"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            research = (
                db_session.query(ResearchHistory)
                .filter(ResearchHistory.id == research_id)
                .first()
            )

            if not research:
                return jsonify({"error": "Research not found"}), 404

            return jsonify(
                {
                    "id": research.id,
                    "query": research.query,
                    "status": research.status,
                    "progress": research.progress,
                    "progress_percentage": research.progress or 0,
                    "mode": research.mode,
                    "created_at": research.created_at,
                    "completed_at": research.completed_at,
                    "report_path": research.report_path,
                    "metadata": research.research_meta,
                }
            )
    except Exception as e:
        logger.exception(f"Error getting research details: {e!s}")
        return jsonify({"error": "An internal error has occurred"}), 500


@research_bp.route("/api/research/<string:research_id>/logs")
@login_required
def get_research_logs(research_id):
    """Get logs for a specific research"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        # First check if the research exists
        with get_user_db_session(username) as db_session:
            research = (
                db_session.query(ResearchHistory)
                .filter_by(id=research_id)
                .first()
            )
            if not research:
                return jsonify({"error": "Research not found"}), 404

            # Get logs from research_logs table
            log_results = (
                db_session.query(ResearchLog)
                .filter_by(research_id=research_id)
                .order_by(ResearchLog.timestamp)
                .all()
            )

        logs = []
        for row in log_results:
            logs.append(
                {
                    "id": row.id,
                    "message": row.message,
                    "timestamp": row.timestamp,
                    "log_type": row.level,
                }
            )

        return jsonify(logs)

    except Exception as e:
        logger.exception(f"Error getting research logs: {e!s}")
        return jsonify({"error": "An internal error has occurred"}), 500


@research_bp.route("/api/report/<string:research_id>")
@login_required
def get_research_report(research_id):
    """Get the research report content"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            # Query using ORM
            research = (
                db_session.query(ResearchHistory)
                .filter_by(id=research_id)
                .first()
            )

            if research is None:
                return jsonify({"error": "Research not found"}), 404

            # Parse metadata if it exists
            metadata = research.research_meta

            # Get report content using storage abstraction
            from ...storage import get_report_storage

            # Get settings snapshot for this thread
            settings_snapshot = (
                metadata.get("settings_snapshot") if metadata else None
            )

            # Pass settings_snapshot to avoid thread context issues
            storage = get_report_storage(
                session=db_session, settings_snapshot=settings_snapshot
            )
            content = storage.get_report(research_id, username)

            if content is None:
                return jsonify({"error": "Report not found"}), 404

            # Return the report data with backwards-compatible fields
            # Examples expect 'summary', 'sources', 'findings' at top level
            return jsonify(
                {
                    "content": content,
                    # Backwards-compatible fields for examples
                    "summary": content,  # The markdown report is the summary
                    "sources": metadata.get("all_links_of_system", []),
                    "findings": metadata.get("findings", []),
                    "metadata": {
                        "title": research.title if research.title else None,
                        "query": research.query,
                        "mode": research.mode if research.mode else None,
                        "created_at": research.created_at
                        if research.created_at
                        else None,
                        "completed_at": research.completed_at
                        if research.completed_at
                        else None,
                        "report_path": research.report_path,
                        **metadata,
                    },
                }
            )

    except Exception as e:
        logger.exception(f"Error getting research report: {e!s}")
        return jsonify({"error": "An internal error has occurred"}), 500


@research_bp.route(
    "/api/v1/research/<research_id>/export/<format>", methods=["POST"]
)
@login_required
def export_research_report(research_id, format):
    """Export research report to different formats (LaTeX, Quarto, RIS, or PDF)"""
    try:
        if format not in ["latex", "quarto", "ris", "pdf"]:
            return jsonify(
                {
                    "error": "Invalid format. Use 'latex', 'quarto', 'ris', or 'pdf'"
                }
            ), 400

        # Get research from database
        username = session.get("username")
        if not username:
            return jsonify({"error": "Not authenticated"}), 401

        try:
            with get_user_db_session(username) as db_session:
                research = (
                    db_session.query(ResearchHistory)
                    .filter_by(id=research_id)
                    .first()
                )
                if not research:
                    return jsonify({"error": "Research not found"}), 404

                # Get report using storage abstraction
                from ...storage import get_report_storage

                # Get metadata for settings snapshot
                metadata = (
                    research.research_meta if research.research_meta else {}
                )
                settings_snapshot = (
                    metadata.get("settings_snapshot") if metadata else None
                )

                storage = get_report_storage(
                    session=db_session, settings_snapshot=settings_snapshot
                )

                # Get report content directly (in memory)
                report_content = storage.get_report(research_id, username)
                if not report_content:
                    return jsonify({"error": "Report content not found"}), 404

                # Export to requested format (all in memory)
                try:
                    # Use title or query for the PDF title
                    pdf_title = research.title or research.query

                    # Generate export content in memory
                    export_content, filename, mimetype = (
                        export_report_to_memory(
                            report_content, format, title=pdf_title
                        )
                    )

                    # Send the file directly from memory
                    return send_file(
                        io.BytesIO(export_content),
                        as_attachment=True,
                        download_name=filename,
                        mimetype=mimetype,
                    )
                except Exception as e:
                    logger.exception(f"Error exporting report: {e!s}")
                    return jsonify(
                        {
                            "error": f"Failed to export to {format}. Please try again later."
                        }
                    ), 500

        except Exception as e:
            logger.exception(f"Error in export endpoint: {e!s}")
            return jsonify({"error": "An internal error has occurred"}), 500

    except Exception as e:
        logger.exception(f"Unexpected error in export endpoint: {e!s}")
        return jsonify({"error": "An internal error has occurred"}), 500


@research_bp.route("/api/research/<string:research_id>/status")
@limiter.exempt
@login_required
def get_research_status(research_id):
    """Get the status of a research process"""
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        with get_user_db_session(username) as db_session:
            research = (
                db_session.query(ResearchHistory)
                .filter_by(id=research_id)
                .first()
            )

            if research is None:
                return jsonify({"error": "Research not found"}), 404

            status = research.status
            progress = research.progress
            completed_at = research.completed_at
            report_path = research.report_path
            metadata = research.research_meta or {}

            # Extract and format error information for better UI display
            error_info = {}
            if metadata and "error" in metadata:
                error_msg = metadata["error"]
                error_type = "unknown"

                # Detect specific error types
                if "timeout" in error_msg.lower():
                    error_type = "timeout"
                    error_info = {
                        "type": "timeout",
                        "message": "LLM service timed out during synthesis. This may be due to high server load or connectivity issues.",
                        "suggestion": "Try again later or use a smaller query scope.",
                    }
                elif (
                    "token limit" in error_msg.lower()
                    or "context length" in error_msg.lower()
                ):
                    error_type = "token_limit"
                    error_info = {
                        "type": "token_limit",
                        "message": "The research query exceeded the AI model's token limit during synthesis.",
                        "suggestion": "Try using a more specific query or reduce the research scope.",
                    }
                elif (
                    "final answer synthesis fail" in error_msg.lower()
                    or "llm error" in error_msg.lower()
                ):
                    error_type = "llm_error"
                    error_info = {
                        "type": "llm_error",
                        "message": "The AI model encountered an error during final answer synthesis.",
                        "suggestion": "Check that your LLM service is running correctly or try a different model.",
                    }
                elif "ollama" in error_msg.lower():
                    error_type = "ollama_error"
                    error_info = {
                        "type": "ollama_error",
                        "message": "The Ollama service is not responding properly.",
                        "suggestion": "Make sure Ollama is running with 'ollama serve' and the model is downloaded.",
                    }
                elif "connection" in error_msg.lower():
                    error_type = "connection"
                    error_info = {
                        "type": "connection",
                        "message": "Connection error with the AI service.",
                        "suggestion": "Check your internet connection and AI service status.",
                    }
                elif metadata.get("solution"):
                    # Use the solution provided in metadata if available
                    error_info = {
                        "type": error_type,
                        "message": error_msg,
                        "suggestion": metadata.get("solution"),
                    }
                else:
                    # Generic error with the original message
                    error_info = {
                        "type": error_type,
                        "message": error_msg,
                        "suggestion": "Try again with a different query or check the application logs.",
                    }

            # Add error_info to the response if it exists
            if error_info:
                metadata["error_info"] = error_info

            # Get the latest milestone log for this research
            latest_milestone = None
            try:
                milestone_log = (
                    db_session.query(ResearchLog)
                    .filter_by(research_id=research_id, level="MILESTONE")
                    .order_by(ResearchLog.timestamp.desc())
                    .first()
                )
                if milestone_log:
                    latest_milestone = {
                        "message": milestone_log.message,
                        "time": milestone_log.timestamp.isoformat()
                        if milestone_log.timestamp
                        else None,
                        "type": "MILESTONE",
                    }
                    logger.debug(
                        f"Found latest milestone for research {research_id}: {milestone_log.message}"
                    )
                else:
                    logger.debug(
                        f"No milestone logs found for research {research_id}"
                    )
            except Exception as e:
                logger.warning(f"Error fetching latest milestone: {e!s}")

            response_data = {
                "status": status,
                "progress": progress,
                "completed_at": completed_at,
                "report_path": report_path,
                "metadata": metadata,
            }

            # Include latest milestone as a log_entry for frontend compatibility
            if latest_milestone:
                response_data["log_entry"] = latest_milestone

            return jsonify(response_data)
    except Exception:
        logger.exception("Error getting research status")
        return jsonify({"error": "Error checking research status"}), 500


@research_bp.route("/api/queue/status", methods=["GET"])
@login_required
def get_queue_status():
    """Get the current queue status for the user"""
    username = session.get("username")

    from ..queue import QueueManager

    try:
        queue_items = QueueManager.get_user_queue(username)

        return jsonify(
            {
                "status": "success",
                "queue": queue_items,
                "total": len(queue_items),
            }
        )
    except Exception:
        logger.exception("Error getting queue status")
        return jsonify(
            {"status": "error", "message": "Failed to process request"}
        ), 500


@research_bp.route("/api/queue/<string:research_id>/position", methods=["GET"])
@login_required
def get_queue_position(research_id):
    """Get the queue position for a specific research"""
    username = session.get("username")

    from ..queue import QueueManager

    try:
        position = QueueManager.get_queue_position(username, research_id)

        if position is None:
            return jsonify(
                {"status": "error", "message": "Research not found in queue"}
            ), 404

        return jsonify({"status": "success", "position": position})
    except Exception:
        logger.exception("Error getting queue position")
        return jsonify(
            {"status": "error", "message": "Failed to process request"}
        ), 500


@research_bp.route("/api/upload/pdf", methods=["POST"])
@login_required
@upload_rate_limit
def upload_pdf():
    """
    Upload and extract text from PDF files with comprehensive security validation.

    Security features:
    - Rate limiting (10 uploads/min, 100/hour per user)
    - File size validation (50MB max per file)
    - File count validation (100 files max)
    - PDF structure validation
    - MIME type validation

    Performance improvements:
    - Single-pass PDF processing (text + metadata)
    - Optimized extraction service
    """
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    try:
        # Early request size validation (before reading any files)
        # This prevents memory exhaustion from chunked encoding attacks
        max_request_size = (
            FileUploadValidator.MAX_FILES_PER_REQUEST
            * FileUploadValidator.MAX_FILE_SIZE
        )
        if request.content_length and request.content_length > max_request_size:
            return jsonify(
                {
                    "error": f"Request too large. Maximum size is {max_request_size // (1024 * 1024)}MB"
                }
            ), 413

        # Check if files are present in the request
        if "files" not in request.files:
            return jsonify({"error": "No files provided"}), 400

        files = request.files.getlist("files")
        if not files or files[0].filename == "":
            return jsonify({"error": "No files selected"}), 400

        # Validate file count
        is_valid, error_msg = FileUploadValidator.validate_file_count(
            len(files)
        )
        if not is_valid:
            return jsonify({"error": error_msg}), 400

        # Get PDF extraction service
        pdf_service = get_pdf_extraction_service()

        extracted_texts = []
        total_files = len(files)
        processed_files = 0
        errors = []

        for file in files:
            if not file or not file.filename:
                errors.append("Unnamed file: Skipped")
                continue

            try:
                # Read file content
                pdf_content = file.read()

                # Comprehensive validation
                is_valid, error_msg = FileUploadValidator.validate_upload(
                    filename=file.filename,
                    file_content=pdf_content,
                    content_length=file.content_length,
                )

                if not is_valid:
                    errors.append(f"{file.filename}: {error_msg}")
                    continue

                # Extract text and metadata in single pass (performance fix)
                result = pdf_service.extract_text_and_metadata(
                    pdf_content, file.filename
                )

                if result["success"]:
                    extracted_texts.append(
                        {
                            "filename": result["filename"],
                            "text": result["text"],
                            "size": result["size"],
                            "pages": result["pages"],
                        }
                    )
                    processed_files += 1
                else:
                    errors.append(f"{file.filename}: {result['error']}")

            except Exception:
                logger.exception(f"Error processing {file.filename}")
                errors.append(f"{file.filename}: Error processing file")

        # Prepare response
        response_data = {
            "status": "success",
            "processed_files": processed_files,
            "total_files": total_files,
            "extracted_texts": extracted_texts,
            "combined_text": "\n\n".join(
                [
                    f"--- From {item['filename']} ---\n{item['text']}"
                    for item in extracted_texts
                ]
            ),
            "errors": errors,
        }

        if processed_files == 0:
            return jsonify(
                {
                    "status": "error",
                    "message": "No files were processed successfully",
                    "errors": errors,
                }
            ), 400

        return jsonify(response_data)

    except Exception:
        logger.exception("Error processing PDF upload")
        return jsonify({"error": "Failed to process PDF files"}), 500
