import requests
from flask import (
    Blueprint,
    current_app,
    jsonify,
    request,
)
from loguru import logger

from ...database.models import QueuedResearch, ResearchHistory
from ...database.session_context import get_user_db_session
from ...config.constants import DEFAULT_OLLAMA_URL
from ...constants import ResearchStatus
from ...utilities.url_utils import normalize_url
from ...security.decorators import require_json_body
from ..auth.decorators import login_required
from ..services.research_service import (
    cancel_research,
)
from ..services.resource_service import (
    add_resource,
    delete_resource,
    get_resources_for_research,
)
from local_deep_research.settings import SettingsManager
from ...security import safe_get, strip_settings_snapshot

# Create blueprint
api_bp = Blueprint("api", __name__)

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


@api_bp.route("/settings/current-config", methods=["GET"])
@login_required
def get_current_config():
    """Get the current configuration from database settings."""
    try:
        with get_user_db_session() as session:
            settings_manager = SettingsManager(session)
            config = {
                "provider": settings_manager.get_setting(
                    "llm.provider", "Not configured"
                ),
                "model": settings_manager.get_setting(
                    "llm.model", "Not configured"
                ),
                "search_tool": settings_manager.get_setting(
                    "search.tool", "searxng"
                ),
                "iterations": settings_manager.get_setting(
                    "search.iterations", 8
                ),
                "questions_per_iteration": settings_manager.get_setting(
                    "search.questions_per_iteration", 5
                ),
                "search_strategy": settings_manager.get_setting(
                    "search.search_strategy", "focused_iteration"
                ),
            }

        return jsonify({"success": True, "config": config})

    except Exception:
        logger.exception("Error getting current config")
        return jsonify(
            {"success": False, "error": "An internal error occurred"}
        ), 500


# API Routes
@api_bp.route("/start", methods=["POST"])
@login_required
def api_start_research():
    """
    Start a new research process.

    Delegates to the full-featured start_research() in research_routes,
    which reads settings from the database, handles queueing, and starts
    the research thread.
    """
    from ..routes.research_routes import start_research

    return start_research()


@api_bp.route("/status/<string:research_id>", methods=["GET"])
@login_required
def api_research_status(research_id):
    """
    Get the status of a research process
    """
    try:
        # Get a fresh session to avoid conflicts with the research process

        with get_user_db_session() 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

            # Extract attributes while session is active
            # to avoid DetachedInstanceError after the with block exits
            result = {
                "status": research.status,
                "progress": research.progress,
                "completed_at": research.completed_at,
                "report_path": research.report_path,
                "metadata": strip_settings_snapshot(research.research_meta),
            }

            # Include queue position for queued research
            if research.status == ResearchStatus.QUEUED:
                queued = (
                    db_session.query(QueuedResearch)
                    .filter_by(research_id=research_id)
                    .first()
                )
                if queued:
                    result["queue_position"] = queued.position

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


@api_bp.route("/terminate/<string:research_id>", methods=["POST"])
@login_required
def api_terminate_research(research_id):
    """
    Terminate a research process
    """
    try:
        from flask import session

        username = session["username"]
        result = cancel_research(research_id, username)
        if result:
            return jsonify(
                {
                    "status": "success",
                    "message": "Research terminated",
                    "result": result,
                }
            )
        return jsonify(
            {
                "status": "success",
                "message": "Research not found or already completed",
                "result": result,
            }
        )
    except Exception:
        logger.exception("Error terminating research")
        return (
            jsonify({"status": "error", "message": "Failed to stop research."}),
            500,
        )


@api_bp.route("/resources/<string:research_id>", methods=["GET"])
@login_required
def api_get_resources(research_id):
    """
    Get resources for a specific research
    """
    try:
        resources = get_resources_for_research(research_id)
        return jsonify({"status": "success", "resources": resources})
    except Exception:
        logger.exception("Error getting resources for research")
        return jsonify(
            {"status": "error", "message": "Failed to get resources"}
        ), 500


@api_bp.route("/resources/<string:research_id>", methods=["POST"])
@login_required
@require_json_body(error_format="status")
def api_add_resource(research_id):
    """
    Add a new resource to a research project
    """
    try:
        data = request.json
        # Required fields
        title = data.get("title")
        url = data.get("url")

        # Optional fields
        content_preview = data.get("content_preview")
        source_type = data.get("source_type", "web")
        metadata = data.get("metadata", {})

        # Validate required fields
        if not title or not url:
            return (
                jsonify(
                    {"status": "error", "message": "Title and URL are required"}
                ),
                400,
            )

        # Security: Validate URL to prevent SSRF attacks
        from ...security.ssrf_validator import validate_url

        is_valid = validate_url(url)
        if not is_valid:
            logger.warning(f"SSRF protection: Rejected URL {url}")
            return (
                jsonify({"status": "error", "message": "Invalid URL"}),
                400,
            )

        # Check if the research exists
        with get_user_db_session() 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

        # Add the resource
        resource_id = add_resource(
            research_id=research_id,
            title=title,
            url=url,
            content_preview=content_preview,
            source_type=source_type,
            metadata=metadata,
        )

        return jsonify(
            {
                "status": "success",
                "message": "Resource added successfully",
                "resource_id": resource_id,
            }
        )
    except Exception:
        logger.exception("Error adding resource")
        return jsonify(
            {"status": "error", "message": "Failed to add resource"}
        ), 500


@api_bp.route(
    "/resources/<string:research_id>/delete/<int:resource_id>",
    methods=["DELETE"],
)
@login_required
def api_delete_resource(research_id, resource_id):
    """
    Delete a resource from a research project
    """
    try:
        # Delete the resource
        success = delete_resource(resource_id)

        if success:
            return jsonify(
                {
                    "status": "success",
                    "message": "Resource deleted successfully",
                }
            )
        return jsonify(
            {"status": "error", "message": "Resource not found"}
        ), 404
    except Exception:
        logger.exception("Error deleting resource")
        return jsonify(
            {
                "status": "error",
                "message": "An internal error occurred while deleting the resource.",
            }
        ), 500


@api_bp.route("/check/ollama_status", methods=["GET"])
@login_required
def check_ollama_status():
    """
    Check if Ollama API is running
    """
    try:
        # Get Ollama URL from config
        llm_config = current_app.config.get("LLM_CONFIG", {})
        provider = llm_config.get("provider", "ollama")

        if provider.lower() != "ollama":
            return jsonify(
                {
                    "running": True,
                    "message": f"Using provider: {provider}, not Ollama",
                }
            )

        # Get Ollama API URL from LLM config
        raw_ollama_base_url = llm_config.get(
            "ollama_base_url", DEFAULT_OLLAMA_URL
        )
        ollama_base_url = (
            normalize_url(raw_ollama_base_url)
            if raw_ollama_base_url
            else DEFAULT_OLLAMA_URL
        )

        logger.info(f"Checking Ollama status at: {ollama_base_url}")

        # Check if Ollama is running
        try:
            response = safe_get(
                f"{ollama_base_url}/api/tags",
                timeout=5,
                allow_localhost=True,
                allow_private_ips=True,
            )

            # Add response details for debugging
            logger.debug(
                f"Ollama status check response code: {response.status_code}"
            )

            if response.status_code == 200:
                # Try to validate the response content
                try:
                    data = response.json()

                    # Check the format
                    if "models" in data:
                        model_count = len(data.get("models", []))
                        logger.info(
                            f"Ollama service is running with {model_count} models (new API format)"
                        )
                    else:
                        # Older API format
                        model_count = len(data)
                        logger.info(
                            f"Ollama service is running with {model_count} models (old API format)"
                        )

                    return jsonify(
                        {
                            "running": True,
                            "message": f"Ollama service is running with {model_count} models",
                            "model_count": model_count,
                        }
                    )
                except ValueError:
                    logger.warning("Ollama returned invalid JSON")
                    # It's running but returned invalid JSON
                    return jsonify(
                        {
                            "running": True,
                            "message": "Ollama service is running but returned invalid data format",
                            "error_details": "Invalid response format from the service.",
                        }
                    )
            else:
                logger.warning(
                    f"Ollama returned non-200 status code: {response.status_code}"
                )
                return jsonify(
                    {
                        "running": False,
                        "message": f"Ollama service returned status code: {response.status_code}",
                        "status_code": response.status_code,
                    }
                )

        except requests.exceptions.ConnectionError:
            logger.warning("Ollama connection error")
            return jsonify(
                {
                    "running": False,
                    "message": "Ollama service is not running or not accessible",
                    "error_type": "connection_error",
                    "error_details": "Unable to connect to the service. Please check if the service is running.",
                }
            )
        except requests.exceptions.Timeout:
            logger.warning("Ollama request timed out")
            return jsonify(
                {
                    "running": False,
                    "message": "Ollama service request timed out after 5 seconds",
                    "error_type": "timeout",
                    "error_details": "Request timed out. The service may be overloaded.",
                }
            )

    except Exception:
        logger.exception("Error checking Ollama status")
        return jsonify(
            {
                "running": False,
                "message": "An internal error occurred while checking Ollama status.",
                "error_type": "exception",
                "error_details": "An internal error occurred.",
            }
        )


@api_bp.route("/check/ollama_model", methods=["GET"])
@login_required
def check_ollama_model():
    """
    Check if the configured Ollama model is available
    """
    try:
        # Get Ollama configuration
        llm_config = current_app.config.get("LLM_CONFIG", {})
        provider = llm_config.get("provider", "ollama")

        if provider.lower() != "ollama":
            return jsonify(
                {
                    "available": True,
                    "message": f"Using provider: {provider}, not Ollama",
                    "provider": provider,
                }
            )

        # Get model name from request or use config default
        model_name = request.args.get("model")
        if not model_name:
            model_name = llm_config.get("model", "gemma3:12b")

        # Log which model we're checking for debugging
        logger.info(f"Checking availability of Ollama model: {model_name}")

        # Get Ollama API URL from LLM config
        raw_ollama_base_url = llm_config.get(
            "ollama_base_url", DEFAULT_OLLAMA_URL
        )
        ollama_base_url = (
            normalize_url(raw_ollama_base_url)
            if raw_ollama_base_url
            else DEFAULT_OLLAMA_URL
        )

        # Check if the model is available
        try:
            response = safe_get(
                f"{ollama_base_url}/api/tags",
                timeout=5,
                allow_localhost=True,
                allow_private_ips=True,
            )

            # Log response details for debugging
            logger.debug(f"Ollama API response status: {response.status_code}")

            if response.status_code != 200:
                logger.warning(
                    f"Ollama API returned non-200 status: {response.status_code}"
                )
                return jsonify(
                    {
                        "available": False,
                        "model": model_name,
                        "message": f"Could not access Ollama service - status code: {response.status_code}",
                        "status_code": response.status_code,
                    }
                )

            # Try to parse the response
            try:
                data = response.json()

                # Debug log the first bit of the response
                response_preview = (
                    str(data)[:500] + "..."
                    if len(str(data)) > 500
                    else str(data)
                )
                logger.debug(f"Ollama API response data: {response_preview}")

                # Get models based on API format
                models = []
                if "models" in data:
                    # Newer Ollama API
                    logger.debug("Using new Ollama API format (models key)")
                    models = data.get("models", [])
                else:
                    # Older Ollama API format
                    logger.debug("Using old Ollama API format (array)")
                    models = data

                # Log available models for debugging
                model_names = [m.get("name", "") for m in models]
                logger.debug(
                    f"Available Ollama models: {', '.join(model_names[:10])}"
                    + (
                        f" and {len(model_names) - 10} more"
                        if len(model_names) > 10
                        else ""
                    )
                )

                # Case-insensitive model name comparison
                model_exists = any(
                    m.get("name", "").lower() == model_name.lower()
                    for m in models
                )

                if model_exists:
                    logger.info(f"Ollama model {model_name} is available")
                    return jsonify(
                        {
                            "available": True,
                            "model": model_name,
                            "message": f"Model {model_name} is available",
                            "all_models": model_names,
                        }
                    )
                # Check if models were found at all
                if not models:
                    logger.warning("No models found in Ollama")
                    message = (
                        "No models found in Ollama. Please pull models first."
                    )
                else:
                    logger.warning(
                        f"Model {model_name} not found among {len(models)} available models"
                    )
                    # Don't expose available models for security reasons
                    message = f"Model {model_name} is not available"

                return jsonify(
                    {
                        "available": False,
                        "model": model_name,
                        "message": message,
                        # Remove all_models to prevent information disclosure
                    }
                )
            except ValueError as json_err:
                # JSON parsing error
                logger.exception(
                    f"Failed to parse Ollama API response: {json_err}"
                )
                return jsonify(
                    {
                        "available": False,
                        "model": model_name,
                        "message": "Invalid response from Ollama API",
                        "error_type": "json_parse_error",
                    }
                )

        except requests.exceptions.ConnectionError:
            # Connection error
            logger.warning("Connection error to Ollama API")
            return jsonify(
                {
                    "available": False,
                    "model": model_name,
                    "message": "Could not connect to Ollama service",
                    "error_type": "connection_error",
                    "error_details": "Unable to connect to the service. Please check if the service is running.",
                }
            )
        except requests.exceptions.Timeout:
            # Timeout error
            logger.warning("Timeout connecting to Ollama API")
            return jsonify(
                {
                    "available": False,
                    "model": model_name,
                    "message": "Connection to Ollama service timed out",
                    "error_type": "timeout",
                }
            )

    except Exception:
        # General exception
        logger.exception("Error checking Ollama model")

        return jsonify(
            {
                "available": False,
                "model": (
                    model_name
                    if "model_name" in locals()
                    else llm_config.get("model", "gemma3:12b")
                ),
                "message": "An internal error occurred while checking the model.",
                "error_type": "exception",
                "error_details": "An internal error occurred.",
            }
        )


# Helper route to get system configuration
@api_bp.route("/config", methods=["GET"])
@login_required
def api_get_config():
    """
    Get public system configuration
    """
    # Only return public configuration
    public_config = {
        "version": current_app.config.get("VERSION", "0.1.0"),
        "llm_provider": current_app.config.get("LLM_CONFIG", {}).get(
            "provider", "ollama"
        ),
        "search_tool": current_app.config.get("SEARCH_CONFIG", {}).get(
            "search_tool", "auto"
        ),
        "features": {
            "notifications": current_app.config.get(
                "ENABLE_NOTIFICATIONS", False
            )
        },
    }

    return jsonify(public_config)
