"""
API module for Local Deep Research.
Provides programmatic access to search and research capabilities.
"""

from datetime import datetime, UTC
from typing import Any, Callable

from loguru import logger
from local_deep_research.settings.logger import log_settings

from ..config.llm_config import get_llm
from ..config.search_config import get_search
from ..config.thread_settings import get_setting_from_snapshot
from ..report_generator import IntegratedReportGenerator
from ..search_system import AdvancedSearchSystem
from ..utilities.db_utils import no_db_settings
from ..utilities.thread_context import clear_search_context, set_search_context
from .settings_utils import create_settings_snapshot


def _close_system(system):
    """Close an AdvancedSearchSystem and its associated resources."""
    from ..utilities.resource_utils import safe_close

    safe_close(system, "search system")
    if hasattr(system, "search"):
        safe_close(system.search, "search engine")
    if hasattr(system, "model"):
        safe_close(system.model, "system LLM")


def _init_search_system(
    model_name: str | None = None,
    temperature: float = 0.7,
    provider: str | None = None,
    openai_endpoint_url: str | None = None,
    progress_callback: Callable[[str, int, dict], None] | None = None,
    search_tool: str | None = None,
    search_strategy: str = "source_based",
    iterations: int = 1,
    questions_per_iteration: int = 1,
    retrievers: dict[str, Any] | None = None,
    llms: dict[str, Any] | None = None,
    username: str | None = None,
    research_id: str | None = None,
    research_context: dict[str, Any] | None = None,
    programmatic_mode: bool = True,
    search_original_query: bool = True,
    settings_snapshot: dict[str, Any] | None = None,
    **kwargs: Any,
) -> AdvancedSearchSystem:
    """
    Initializes the advanced search system with specified parameters. This function sets up
    and returns an instance of the AdvancedSearchSystem using the provided configuration
    options such as model name, temperature for randomness in responses, provider service
    details, endpoint URL, and an optional search tool.

    Args:
        model_name: Name of the model to use (if None, uses database setting)
        temperature: LLM temperature for generation
        provider: Provider to use (if None, uses database setting)
        openai_endpoint_url: Custom endpoint URL to use (if None, uses database
            setting)
        progress_callback: Optional callback function to receive progress updates
        search_tool: Search engine to use (searxng, wikipedia, arxiv, etc.). If None, uses default
        search_strategy: Search strategy to use (modular, source_based, etc.). If None, uses default
        iterations: Number of research cycles to perform
        questions_per_iteration: Number of questions to generate per cycle
        search_strategy: The name of the search strategy to use.
        retrievers: Optional dictionary of {name: retriever} pairs to use as search engines
        llms: Optional dictionary of {name: llm} pairs to use as language models
        programmatic_mode: If True, disables database operations and metrics tracking
        search_original_query: Whether to include the original query in the first iteration of search

    Returns:
        AdvancedSearchSystem: An instance of the configured AdvancedSearchSystem.

    """
    # Register retrievers if provided
    if retrievers:
        from ..web_search_engines.retriever_registry import retriever_registry

        retriever_registry.register_multiple(retrievers)
        logger.info(
            f"Registered {len(retrievers)} retrievers: {list(retrievers.keys())}"
        )

    # Register LLMs if provided
    if llms:
        from ..llm import register_llm

        for name, llm_instance in llms.items():
            register_llm(name, llm_instance)
        logger.info(f"Registered {len(llms)} LLMs: {list(llms.keys())}")

    # Use settings_snapshot from parameter, or fall back to kwargs
    if settings_snapshot is None:
        settings_snapshot = kwargs.get("settings_snapshot")

    # Get language model with custom temperature
    llm = get_llm(
        temperature=temperature,
        openai_endpoint_url=openai_endpoint_url,
        model_name=model_name,
        provider=provider,
        research_id=research_id,
        research_context=research_context,
        settings_snapshot=settings_snapshot,
    )

    # Set the search engine if specified or get from settings
    search_engine = None

    try:
        # If no search_tool provided, get from settings_snapshot
        if not search_tool and settings_snapshot:
            search_tool = get_setting_from_snapshot(
                "search.tool", settings_snapshot=settings_snapshot
            )

        if search_tool:
            search_engine = get_search(
                search_tool,
                llm_instance=llm,
                username=username,
                settings_snapshot=settings_snapshot,
                programmatic_mode=programmatic_mode,
            )
            if search_engine is None:
                logger.warning(
                    f"Could not create search engine '{search_tool}', using default."
                )

        # Create search system with custom parameters
        logger.info("Search strategy: {}", search_strategy)
        system = AdvancedSearchSystem(
            llm=llm,
            search=search_engine,
            strategy_name=search_strategy,
            username=username,
            research_id=research_id,
            research_context=research_context,
            settings_snapshot=settings_snapshot,
            programmatic_mode=programmatic_mode,
            search_original_query=search_original_query,
        )
    except Exception:
        from ..utilities.resource_utils import safe_close

        safe_close(llm, "init LLM")
        raise

    # Override default settings with user-provided values
    system.max_iterations = iterations
    system.questions_per_iteration = questions_per_iteration

    # Set progress callback if provided
    if progress_callback:
        system.set_progress_callback(progress_callback)

    return system


@no_db_settings
def quick_summary(
    query: str,
    research_id: str | None = None,
    retrievers: dict[str, Any] | None = None,
    llms: dict[str, Any] | None = None,
    username: str | None = None,
    provider: str | None = None,
    api_key: str | None = None,
    temperature: float | None = None,
    max_search_results: int | None = None,
    settings: dict[str, Any] | None = None,
    settings_override: dict[str, Any] | None = None,
    search_original_query: bool = True,
    **kwargs: Any,
) -> dict[str, Any]:
    """
    Generate a quick research summary for a given query.

    Args:
        query: The research query to analyze
        research_id: Optional research ID (int or UUID string) for tracking metrics
        retrievers: Optional dictionary of {name: retriever} pairs to use as search engines
        llms: Optional dictionary of {name: llm} pairs to use as language models
        provider: LLM provider to use (e.g., 'openai', 'anthropic'). For programmatic API only.
        api_key: API key for the provider. For programmatic API only.
        temperature: LLM temperature (0.0-1.0). For programmatic API only.
        max_search_results: Maximum number of search results to return. For programmatic API only.
        settings: Base settings dict to use instead of defaults. For programmatic API only.
        settings_override: Dictionary of settings to override (e.g., {"llm.max_tokens": 4000}). For programmatic API only.
        search_original_query: Whether to include the original query in the first iteration of search.
            Set to False for news searches to avoid sending long subscription prompts to search engines.
        **kwargs: Additional configuration for the search system. Will be forwarded to
            `_init_search_system()`.

    Returns:
        Dictionary containing the research results with keys:
        - 'summary': The generated summary text
        - 'findings': List of detailed findings from each search
        - 'iterations': Number of iterations performed
        - 'questions': Questions generated during research

    Examples:
        # Simple usage with defaults
        result = quick_summary("What is quantum computing?")

        # With custom provider
        result = quick_summary(
            "What is quantum computing?",
            provider="anthropic",
            api_key="sk-ant-..."
        )

        # With advanced settings
        result = quick_summary(
            "What is quantum computing?",
            temperature=0.2,
            settings_override={"search.engines.arxiv.enabled": True}
        )
    """
    logger.info("Generating quick summary for query: {}", query)

    if "settings_snapshot" not in kwargs:
        snapshot_kwargs = {}
        if provider is not None:
            snapshot_kwargs["provider"] = provider
        if api_key is not None:
            snapshot_kwargs["api_key"] = api_key
        if temperature is not None:
            snapshot_kwargs["temperature"] = temperature
        if max_search_results is not None:
            snapshot_kwargs["max_search_results"] = max_search_results

        if (
            not snapshot_kwargs
            and settings is None
            and settings_override is None
        ):
            logger.warning(
                "No settings_snapshot or explicit config provided to quick_summary(). "
                "Using defaults and environment variables. For explicit control, "
                "pass settings_snapshot=create_settings_snapshot(...)."
            )

        kwargs["settings_snapshot"] = create_settings_snapshot(
            base_settings=settings,
            overrides=settings_override,
            **snapshot_kwargs,
        )
        log_settings(
            kwargs["settings_snapshot"],
            "Created settings snapshot for programmatic API",
        )
    else:
        log_settings(
            kwargs["settings_snapshot"],
            "Using provided settings snapshot for programmatic API",
        )

    # Generate a research_id if none provided
    if research_id is None:
        import uuid

        research_id = str(uuid.uuid4())
        logger.debug(f"Generated research_id: {research_id}")

    # Register retrievers if provided
    if retrievers:
        from ..web_search_engines.retriever_registry import retriever_registry

        retriever_registry.register_multiple(retrievers)
        logger.info(
            f"Registered {len(retrievers)} retrievers: {list(retrievers.keys())}"
        )

    # Register LLMs if provided
    if llms:
        from ..llm import register_llm

        for name, llm_instance in llms.items():
            register_llm(name, llm_instance)
        logger.info(f"Registered {len(llms)} LLMs: {list(llms.keys())}")

    search_context = {
        "research_id": research_id,  # Pass UUID or integer directly
        "research_query": query,
        "research_mode": kwargs.get("research_mode", "quick"),
        "research_phase": "init",
        "search_iteration": 0,
        "search_engine_selected": kwargs.get("search_tool"),
        "username": username,  # Include username for metrics tracking
        "user_password": kwargs.get(
            "user_password"
        ),  # Include password for metrics tracking
        # Thread-safe settings snapshot propagated to background search
        # threads (engine config, per-user resolution, egress scope).
        "settings_snapshot": kwargs.get("settings_snapshot") or {},
    }
    set_search_context(search_context)

    system = None
    try:
        # Remove research_mode from kwargs before passing to _init_search_system
        init_kwargs = {k: v for k, v in kwargs.items() if k != "research_mode"}
        # Make sure username is passed to the system
        init_kwargs["username"] = username
        init_kwargs["research_id"] = research_id
        init_kwargs["research_context"] = search_context
        init_kwargs["search_original_query"] = search_original_query
        system = _init_search_system(llms=llms, **init_kwargs)

        # Perform the search and analysis
        results = system.analyze_topic(query)

        # Extract the summary from the current knowledge
        if results and "current_knowledge" in results:
            summary = results["current_knowledge"]
        else:
            summary = "Unable to generate summary for the query."

        # Prepare the return value (guard against None results)
        if results is None:
            results = {}
        return {
            "research_id": research_id,
            "summary": summary,
            "findings": results.get("findings", []),
            "iterations": results.get("iterations", 0),
            "questions": results.get("questions", {}),
            "formatted_findings": results.get("formatted_findings", ""),
            "sources": results.get("all_links_of_system", []),
        }
    finally:
        if system is not None:
            _close_system(system)
        clear_search_context()


@no_db_settings
def generate_report(
    query: str,
    output_file: str | None = None,
    progress_callback: Callable | None = None,
    searches_per_section: int = 2,
    retrievers: dict[str, Any] | None = None,
    llms: dict[str, Any] | None = None,
    username: str | None = None,
    provider: str | None = None,
    api_key: str | None = None,
    temperature: float | None = None,
    max_search_results: int | None = None,
    settings: dict[str, Any] | None = None,
    settings_override: dict[str, Any] | None = None,
    **kwargs: Any,
) -> dict[str, Any]:
    """
    Generate a comprehensive, structured research report for a given query.

    Args:
        query: The research query to analyze
        output_file: Optional path to save report markdown file
        progress_callback: Optional callback function to receive progress updates
        searches_per_section: The number of searches to perform for each
            section in the report.
        retrievers: Optional dictionary of {name: retriever} pairs to use as search engines
        llms: Optional dictionary of {name: llm} pairs to use as language models
        provider: LLM provider to use (e.g., 'openai', 'anthropic'). For programmatic API only.
        api_key: API key for the provider. For programmatic API only.
        temperature: LLM temperature (0.0-1.0). For programmatic API only.
        max_search_results: Maximum number of search results to return. For programmatic API only.
        settings: Base settings dict to use instead of defaults. For programmatic API only.
        settings_override: Dictionary of settings to override. For programmatic API only.
        **kwargs: Additional configuration for the search system.

    Returns:
        Dictionary containing the research report with keys:
        - 'content': The full report content in markdown format
        - 'metadata': Report metadata including generated timestamp and query
        - 'file_path': Path to saved file (if output_file was provided)

    Examples:
        # Simple usage with settings snapshot
        from local_deep_research.api.settings_utils import create_settings_snapshot
        settings = create_settings_snapshot({"programmatic_mode": True})
        result = generate_report("AI research", settings_snapshot=settings)

        # Save to file
        result = generate_report(
            "AI research",
            output_file="report.md",
            settings_snapshot=settings
        )
    """
    logger.info("Generating comprehensive research report for query: {}", query)

    if "settings_snapshot" not in kwargs:
        snapshot_kwargs = {}
        if provider is not None:
            snapshot_kwargs["provider"] = provider
        if api_key is not None:
            snapshot_kwargs["api_key"] = api_key
        if temperature is not None:
            snapshot_kwargs["temperature"] = temperature
        if max_search_results is not None:
            snapshot_kwargs["max_search_results"] = max_search_results

        if (
            not snapshot_kwargs
            and settings is None
            and settings_override is None
        ):
            logger.warning(
                "No settings_snapshot or explicit config provided to generate_report(). "
                "Using defaults and environment variables. For explicit control, "
                "pass settings_snapshot=create_settings_snapshot(...)."
            )

        kwargs["settings_snapshot"] = create_settings_snapshot(
            base_settings=settings,
            overrides=settings_override,
            **snapshot_kwargs,
        )
        log_settings(
            kwargs["settings_snapshot"],
            "Created settings snapshot for programmatic API",
        )
    else:
        log_settings(
            kwargs["settings_snapshot"],
            "Using provided settings snapshot for programmatic API",
        )

    # Register retrievers if provided
    if retrievers:
        from ..web_search_engines.retriever_registry import retriever_registry

        retriever_registry.register_multiple(retrievers)
        logger.info(
            f"Registered {len(retrievers)} retrievers: {list(retrievers.keys())}"
        )

    # Register LLMs if provided
    if llms:
        from ..llm import register_llm

        for name, llm_instance in llms.items():
            register_llm(name, llm_instance)
        logger.info(f"Registered {len(llms)} LLMs: {list(llms.keys())}")

    import uuid

    search_context = {
        "research_id": str(uuid.uuid4()),
        "research_query": query,
        "research_mode": "report",
        "research_phase": "init",
        "search_iteration": 0,
        "search_engine_selected": kwargs.get("search_tool"),
        "username": username,
        "user_password": kwargs.get("user_password"),
        "settings_snapshot": kwargs.get("settings_snapshot") or {},
    }
    set_search_context(search_context)

    system = None
    try:
        system = _init_search_system(
            retrievers=retrievers, llms=llms, username=username, **kwargs
        )
        # Set progress callback if provided
        if progress_callback:
            system.set_progress_callback(progress_callback)

        # Perform the initial research
        initial_findings = system.analyze_topic(query)

        # Generate the structured report
        report_generator = IntegratedReportGenerator(
            search_system=system,
            llm=system.model,
            searches_per_section=searches_per_section,
            settings_snapshot=kwargs.get("settings_snapshot"),
        )
        report = report_generator.generate_report(initial_findings, query)

        # Save report to file if path is provided
        if output_file and report and "content" in report:
            from ..security.file_write_verifier import write_file_verified

            write_file_verified(
                output_file,
                report["content"],
                "api.allow_file_output",
                context="API research report",
                settings_snapshot=kwargs.get("settings_snapshot"),
            )
            logger.info(f"Report saved to {output_file}")
            report["file_path"] = output_file
        return report
    finally:
        if system is not None:
            _close_system(system)
        clear_search_context()


@no_db_settings
def detailed_research(
    query: str,
    research_id: str | None = None,
    retrievers: dict[str, Any] | None = None,
    llms: dict[str, Any] | None = None,
    username: str | None = None,
    **kwargs: Any,
) -> dict[str, Any]:
    """
    Perform detailed research with comprehensive analysis.

    Similar to generate_report but returns structured data instead of markdown.

    Args:
        query: The research query to analyze
        research_id: Optional research ID (int or UUID string) for tracking metrics
        retrievers: Optional dictionary of {name: retriever} pairs to use as search engines
        llms: Optional dictionary of {name: llm} pairs to use as language models
        username: Optional username for per-user cache isolation
        **kwargs: Configuration for the search system. Pass settings_snapshot
            (via create_settings_snapshot()) to configure provider, temperature, etc.

    Returns:
        Dictionary containing detailed research results
    """
    logger.info("Performing detailed research for query: {}", query)

    if "settings_snapshot" not in kwargs:
        logger.warning(
            "No settings_snapshot provided to detailed_research(). "
            "Using defaults and environment variables. For explicit control, "
            "pass settings_snapshot=create_settings_snapshot(provider=..., "
            "overrides={'search.tool': ...})."
        )
        kwargs["settings_snapshot"] = create_settings_snapshot()

    # Generate a research_id if none provided
    if research_id is None:
        import uuid

        research_id = str(uuid.uuid4())
        logger.debug(f"Generated research_id: {research_id}")

    # Register retrievers if provided
    if retrievers:
        from ..web_search_engines.retriever_registry import retriever_registry

        retriever_registry.register_multiple(retrievers)
        logger.info(
            f"Registered {len(retrievers)} retrievers: {list(retrievers.keys())}"
        )

    # Register LLMs if provided
    if llms:
        from ..llm import register_llm

        for name, llm_instance in llms.items():
            register_llm(name, llm_instance)
        logger.info(f"Registered {len(llms)} LLMs: {list(llms.keys())}")

    search_context = {
        "research_id": research_id,
        "research_query": query,
        "research_mode": "detailed",
        "research_phase": "init",
        "search_iteration": 0,
        "search_engine_selected": kwargs.get("search_tool"),
        "username": username,
        "user_password": kwargs.get("user_password"),
        "settings_snapshot": kwargs.get("settings_snapshot") or {},
    }
    set_search_context(search_context)

    system = None
    try:
        # Initialize system
        system = _init_search_system(
            retrievers=retrievers, llms=llms, username=username, **kwargs
        )

        # Perform detailed research
        results = system.analyze_topic(query)

        # Return comprehensive results (guard against None results)
        if results is None:
            results = {}
        return {
            "query": query,
            "research_id": research_id,
            "summary": results.get("current_knowledge", ""),
            "findings": results.get("findings", []),
            "iterations": results.get("iterations", 0),
            "questions": results.get("questions", {}),
            "formatted_findings": results.get("formatted_findings", ""),
            "sources": results.get("all_links_of_system", []),
            "metadata": {
                "timestamp": datetime.now(UTC).isoformat(),
                "search_tool": kwargs.get("search_tool", "searxng"),
                "iterations_requested": kwargs.get("iterations", 1),
                "strategy": kwargs.get("search_strategy", "source_based"),
            },
        }
    finally:
        if system is not None:
            _close_system(system)
        clear_search_context()


@no_db_settings
def analyze_documents(
    query: str,
    collection_name: str,
    max_results: int = 10,
    temperature: float = 0.7,
    force_reindex: bool = False,
    output_file: str | None = None,
    *,
    username: str | None = None,
    settings_snapshot: dict[str, Any] | None = None,
    programmatic_mode: bool = True,
) -> dict[str, Any]:
    """
    Search and analyze documents in a specific local collection.

    Args:
        query: The search query
        collection_name: Name of the local document collection to search
        max_results: Maximum number of results to return
        temperature: LLM temperature for summary generation
        force_reindex: Whether to force reindexing the collection
        output_file: Optional path to save analysis results to a file
        username: Optional username for thread context. REST callers pass the
            authenticated user; programmatic SDK callers can omit.
        settings_snapshot: Settings snapshot for the user's stored configuration
            (LLM provider/model, embedding model, etc.). REST callers pass the
            snapshot from the user's encrypted DB; SDK callers can omit to use
            JSON defaults + LDR_* env vars.
        programmatic_mode: If True (default for SDK callers), disables DB-backed
            metrics. REST callers pass False so per-user rate-limit estimates
            persist across requests.

    Returns:
        Dictionary containing:
        - 'summary': Summary of the findings
        - 'documents': List of matching documents with content and metadata
    """
    if settings_snapshot is None:
        settings_snapshot = create_settings_snapshot()

    logger.info(
        f"Analyzing documents in collection '{collection_name}' for query: {query}"
    )

    llm = None
    search = None
    try:
        # Get language model with custom temperature
        llm = get_llm(
            temperature=temperature, settings_snapshot=settings_snapshot
        )

        # Get search engine for the specified collection
        search = get_search(
            collection_name,
            llm_instance=llm,
            username=username,
            settings_snapshot=settings_snapshot,
            programmatic_mode=programmatic_mode,
        )

        if not search:
            from ..utilities.resource_utils import safe_close

            safe_close(llm, "LLM")
            llm = None
            return {
                "summary": f"Error: Collection '{collection_name}' not found or not properly configured.",
                "documents": [],
            }

        # Set max results
        search.max_results = max_results
        # Perform the search
        results = search.run(query)

        if not results:
            return {
                "summary": f"No documents found in collection '{collection_name}' for query: '{query}'",
                "documents": [],
            }

        # Get LLM to generate a summary of the results

        docs_text = "\n\n".join(
            [
                f"Document {i + 1}:"
                f" {doc.get('content', doc.get('snippet', ''))[:1000]}"
                for i, doc in enumerate(results[:5])
            ]
        )  # Limit to first 5 docs and 1000 chars each

        summary_prompt = f"""Analyze these document excerpts related to the query: "{query}"

        {docs_text}

        Provide a concise summary of the key information found in these documents related to the query.
        """

        import time

        llm_start_time = time.time()
        logger.info(
            f"Starting LLM summary generation (prompt length: {len(summary_prompt)} chars)..."
        )

        summary_response = llm.invoke(summary_prompt)

        llm_elapsed = time.time() - llm_start_time
        logger.info(f"LLM summary generation completed in {llm_elapsed:.2f}s")

        if hasattr(summary_response, "content"):
            summary = summary_response.content
        else:
            summary = str(summary_response)

        # Create result dictionary
        analysis_result = {
            "summary": summary,
            "documents": results,
            "collection": collection_name,
            "document_count": len(results),
        }

        # Save to file if requested
        if output_file:
            from ..security.file_write_verifier import write_file_verified

            content = f"# Document Analysis: {query}\n\n"
            content += f"## Summary\n\n{summary}\n\n"
            content += f"## Documents Found: {len(results)}\n\n"

            for i, doc in enumerate(results):
                content += (
                    f"### Document {i + 1}: {doc.get('title', 'Untitled')}\n\n"
                )
                content += f"**Source:** {doc.get('link', 'Unknown')}\n\n"
                content += f"**Content:**\n\n{doc.get('content', doc.get('snippet', 'No content available'))[:1000]}...\n\n"
                content += "---\n\n"

            write_file_verified(
                output_file,
                content,
                "api.allow_file_output",
                context="API document analysis",
                settings_snapshot=settings_snapshot,
            )

            analysis_result["file_path"] = output_file
            logger.info(f"Analysis saved to {output_file}")

        return analysis_result
    finally:
        from ..utilities.resource_utils import safe_close

        safe_close(search, "search engine")
        safe_close(llm, "LLM")
