"""
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 ..utilities.search_utilities import remove_think_tags
from .settings_utils import create_settings_snapshot


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 (auto, 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

    # 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,
    )

    # 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: %s", query)

    # Only create settings snapshot if not already provided (programmatic API)
    if "settings_snapshot" not in kwargs:
        # Build kwargs for create_settings_snapshot from explicit parameters
        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

        # Create settings snapshot for programmatic use (only if not already provided)
        if "settings_snapshot" not in kwargs:
            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
    }
    set_search_context(search_context)

    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
        return {
            "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:
        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,
    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: %s", query)

    # Only create settings snapshot if not already provided (programmatic API)
    if "settings_snapshot" not in kwargs:
        # Build kwargs for create_settings_snapshot from explicit parameters
        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

        # Create settings snapshot for programmatic use (only if not already provided)
        if "settings_snapshot" not in kwargs:
            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())}")

    system = _init_search_system(retrievers=retrievers, llms=llms, **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


@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,
    **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
        **kwargs: Configuration for the search system

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

    # 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"),
    }
    set_search_context(search_context)

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

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

        # Return comprehensive 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", "auto"),
                "iterations_requested": kwargs.get("iterations", 1),
                "strategy": kwargs.get("search_strategy", "source_based"),
            },
        }
    finally:
        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,
) -> 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

    Returns:
        Dictionary containing:
        - 'summary': Summary of the findings
        - 'documents': List of matching documents with content and metadata
    """
    logger.info(
        f"Analyzing documents in collection '{collection_name}' for query: {query}"
    )

    # Get language model with custom temperature
    llm = get_llm(temperature=temperature)

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

    if not search:
        return {
            "summary": f"Error: Collection '{collection_name}' not found or not properly configured.",
            "documents": [],
        }

    # Set max results
    search.max_results = max_results

    # Force reindex if requested
    if force_reindex and hasattr(search, "embedding_manager"):
        for folder_path in search.folder_paths:
            search.embedding_manager.index_folder(
                folder_path, force_reindex=True
            )

    # 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 = remove_think_tags(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=None,  # analyze_documents doesn't support programmatic mode yet
        )

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

    return analysis_result
