import concurrent.futures
from typing import Dict

from loguru import logger

from ...citation_handler import CitationHandler
# LLM and search instances should be passed via constructor, not imported

# Removed get_db_setting import - using settings_snapshot instead
from ...utilities.thread_context import (
    preserve_research_context,
    get_search_context,
)
from ...utilities.threading_utils import thread_context, thread_with_app_context
from ..filters.cross_engine_filter import CrossEngineFilter
from ..findings.repository import FindingsRepository
from ..questions.atomic_fact_question import AtomicFactQuestionGenerator
from ..questions.standard_question import StandardQuestionGenerator
from .base_strategy import BaseSearchStrategy


class SourceBasedSearchStrategy(BaseSearchStrategy):
    """
    Source-based search strategy that generates questions based on search results and
    defers content analysis until final synthesis.
    """

    def __init__(
        self,
        search,
        model,
        citation_handler=None,
        include_text_content: bool = True,
        use_cross_engine_filter: bool = True,
        filter_reorder: bool = True,
        filter_reindex: bool = True,
        cross_engine_max_results: int = None,
        all_links_of_system=None,
        use_atomic_facts: bool = False,
        settings_snapshot=None,
        search_original_query: bool = True,
    ):
        """Initialize with optional dependency injection for testing."""
        # Pass the links list and settings to the parent class
        super().__init__(
            all_links_of_system=all_links_of_system,
            settings_snapshot=settings_snapshot,
            search_original_query=search_original_query,
        )

        # Model and search are always provided by AdvancedSearchSystem
        self.model = model
        self.search = search
        # Note: progress_callback and questions_by_iteration are already set by parent class

        self.include_text_content = include_text_content
        self.use_cross_engine_filter = use_cross_engine_filter
        self.filter_reorder = filter_reorder
        self.filter_reindex = filter_reindex

        # Initialize the cross-engine filter
        self.cross_engine_filter = CrossEngineFilter(
            model=self.model,
            max_results=cross_engine_max_results,
            default_reorder=filter_reorder,
            default_reindex=filter_reindex,
            settings_snapshot=settings_snapshot,
        )

        # Set include_full_content on the search engine if it supports it
        if hasattr(self.search, "include_full_content"):
            self.search.include_full_content = include_text_content

        # Use provided citation_handler or create one
        self.citation_handler = citation_handler or CitationHandler(self.model)

        # Initialize components
        if use_atomic_facts:
            self.question_generator = AtomicFactQuestionGenerator(self.model)
        else:
            self.question_generator = StandardQuestionGenerator(self.model)
        self.findings_repository = FindingsRepository(self.model)

    def _format_search_results_as_context(self, search_results):
        """Format search results into context for question generation."""
        context_snippets = []

        for i, result in enumerate(
            search_results[:10]
        ):  # Limit to prevent context overflow
            title = result.get("title", "Untitled")
            snippet = result.get("snippet", "")
            url = result.get("link", "")

            if snippet:
                context_snippets.append(
                    f"Source {i + 1}: {title}\nURL: {url}\nSnippet: {snippet}"
                )

        return "\n\n".join(context_snippets)

    def analyze_topic(self, query: str) -> Dict:
        """
        Analyze a topic using source-based search strategy.
        """
        logger.info(f"Starting source-based research on topic: {query}")
        accumulated_search_results_across_all_iterations = []  # tracking links across iterations but not global
        findings = []
        total_citation_count_before_this_search = len(self.all_links_of_system)

        self._update_progress(
            "Initializing source-based research",
            5,
            {
                "phase": "init",
                "strategy": "source-based",
                "include_text_content": self.include_text_content,
            },
        )

        # Check search engine
        if not self._validate_search_engine():
            return {
                "findings": [],
                "iterations": 0,
                "questions_by_iteration": {},
                "formatted_findings": "Error: Unable to conduct research without a search engine.",
                "current_knowledge": "",
                "error": "No search engine available",
            }

        # Determine number of iterations to run
        iterations_to_run = self.get_setting("search.iterations", 2)
        iterations_to_run = int(iterations_to_run)
        questions_per_iteration = self.get_setting("search.questions", 3)

        logger.info(
            f"SourceBasedStrategy configuration - iterations: {iterations_to_run}, questions_per_iteration: {questions_per_iteration}"
        )
        logger.debug(
            f"SourceBasedStrategy settings - include_text_content: {self.include_text_content}, use_cross_engine_filter: {self.use_cross_engine_filter}"
        )
        try:
            filtered_search_results = []
            total_citation_count_before_this_search = len(
                self.all_links_of_system
            )
            # Run each iteration
            for iteration in range(1, iterations_to_run + 1):
                iteration_progress_base = 5 + (iteration - 1) * (
                    70 / iterations_to_run
                )

                # Step 1: Generate or use questions
                # Show context-aware progress message (includes iteration info)
                self._emit_question_generation_progress(
                    iteration=iteration,
                    progress_percent=iteration_progress_base + 5,
                    source_count=len(filtered_search_results)
                    if iteration > 1
                    else 0,
                    query=query,
                )

                # For first iteration, use initial query
                if iteration == 1:
                    # Check if user query is too long for direct search
                    max_query_length = self.get_setting(
                        "app.max_user_query_length", 300
                    )
                    original_search_original_query = self.search_original_query

                    if (
                        self.search_original_query
                        and len(query.strip()) > max_query_length
                    ):
                        logger.warning(
                            f"Long user query detected ({len(query.strip())} chars > {max_query_length} limit), "
                            "using LLM questions only for search"
                        )
                        self.search_original_query = False

                    # Generate questions for first iteration
                    context = (
                        f"""Iteration: {iteration} of {iterations_to_run}"""
                    )
                    questions = self.question_generator.generate_questions(
                        current_knowledge=context,
                        query=query,
                        questions_per_iteration=int(
                            self.get_setting(
                                "search.questions_per_iteration", 5
                            )  # Default to 5 if not set
                        ),
                        questions_by_iteration=self.questions_by_iteration,
                    )

                    # Include original query if enabled and not already present
                    all_questions = (
                        [query] + questions
                        if self.search_original_query and query not in questions
                        else questions
                    )

                    if not self.search_original_query:
                        logger.info(
                            "search_original_query=False - skipping original query"
                        )

                    self.questions_by_iteration[iteration] = all_questions
                    logger.info(
                        f"Using questions for iteration {iteration}: {all_questions}"
                    )

                    # Restore original search_original_query setting after first iteration
                    if (
                        original_search_original_query
                        != self.search_original_query
                    ):
                        self.search_original_query = (
                            original_search_original_query
                        )
                        logger.debug(
                            "Restored original search_original_query setting after first iteration"
                        )

                else:
                    # For subsequent iterations, generate questions based on previous search results
                    source_context = self._format_search_results_as_context(
                        filtered_search_results
                    )
                    if iteration != 1:
                        context = f"""Previous search results:\n{source_context}\n\nIteration: {iteration} of {iterations_to_run}"""
                    elif iterations_to_run == 1:
                        context = ""
                    else:
                        context = (
                            f"""Iteration: {iteration} of {iterations_to_run}"""
                        )
                    # Use standard question generator with search results as context
                    questions = self.question_generator.generate_questions(
                        current_knowledge=context,
                        query=query,
                        questions_per_iteration=int(
                            self.get_setting(
                                "search.questions_per_iteration", 2
                            )
                        ),
                        questions_by_iteration=self.questions_by_iteration,
                    )

                    # Use only the new questions for this iteration's searches
                    all_questions = questions

                    # Store in questions_by_iteration
                    self.questions_by_iteration[iteration] = questions
                    logger.info(
                        f"Generated questions for iteration {iteration}: {questions}"
                    )

                # Skip if no questions (all_questions may include original query in iteration 1)
                if not all_questions:
                    logger.warning(
                        f"No questions generated for iteration {iteration}, skipping search phase"
                    )
                    continue

                # Step 2: Run all searches in parallel for this iteration
                # Function for thread pool
                @thread_with_app_context
                @preserve_research_context
                def search_question(q):
                    try:
                        current_context = get_search_context()
                        result = self.search.run(
                            q, research_context=current_context
                        )
                        return {"question": q, "results": result or []}
                    except Exception as e:
                        logger.exception(f"Error searching for '{q}': {e!s}")
                        return {
                            "question": q,
                            "results": [],
                            "error": "Search failed",
                        }

                # Run searches in parallel
                with concurrent.futures.ThreadPoolExecutor(
                    max_workers=len(all_questions)
                ) as executor:
                    futures = [
                        executor.submit(search_question, thread_context(), q)
                        for q in all_questions
                    ]
                    iteration_search_dict = {}
                    iteration_search_results = []

                    # Process results as they complete (no per-search progress to avoid jumps)
                    for future in concurrent.futures.as_completed(futures):
                        result_dict = future.result()
                        question = result_dict["question"]
                        search_results = result_dict["results"]
                        iteration_search_dict[question] = search_results
                        iteration_search_results.extend(search_results)

                if False and self.use_cross_engine_filter:
                    self._update_progress(
                        f"Filtering search results for iteration {iteration}",
                        iteration_progress_base + 45,
                        {
                            "phase": "cross_engine_filtering",
                            "iteration": iteration,
                        },
                    )

                    existing_link_count = len(self.all_links_of_system)
                    logger.info(f"Existing link count: {existing_link_count}")
                    filtered_search_results = self.cross_engine_filter.filter_results(
                        iteration_search_results,
                        query,
                        reorder=True,
                        reindex=True,
                        start_index=existing_link_count,  # Start indexing after existing links
                    )

                    self._update_progress(
                        f"Filtered from {len(iteration_search_results)} to {len(filtered_search_results)} results",
                        iteration_progress_base + 50,
                        {
                            "phase": "filtering_complete",
                            "iteration": iteration,
                            "links_count": len(self.all_links_of_system),
                        },
                    )
                else:
                    # Use the search results as they are
                    filtered_search_results = iteration_search_results

                    # Use filtered results
                accumulated_search_results_across_all_iterations.extend(
                    filtered_search_results
                )

                # Create a lightweight finding for this iteration's search metadata (no text content)
                finding = {
                    "phase": f"Iteration {iteration}",
                    "content": f"Searched with {len(all_questions)} questions, found {len(filtered_search_results)} results.",
                    "question": query,
                    "documents": [],
                }
                findings.append(finding)

            # Do we need this filter?
            if self.use_cross_engine_filter:
                # Final filtering of all accumulated search results
                self._update_progress(
                    f"Filtering {len(accumulated_search_results_across_all_iterations)} results for relevance...",
                    80,
                    {"phase": "final_filtering", "type": "milestone"},
                )
                final_filtered_results = (
                    self.cross_engine_filter.filter_results(
                        accumulated_search_results_across_all_iterations,
                        query,
                        reorder=True,  # Always reorder in final filtering
                        reindex=True,  # Always reindex in final filtering
                        max_results=int(
                            self.get_setting("search.final_max_results", 100)
                        ),
                        start_index=len(self.all_links_of_system),
                    )
                )
                self._update_progress(
                    f"Filtered from {len(accumulated_search_results_across_all_iterations)} to {len(final_filtered_results)} results",
                    iteration_progress_base + 85,
                    {
                        "phase": "filtering_complete",
                        "iteration": iteration,
                        "links_count": len(self.all_links_of_system),
                    },
                )
            else:
                final_filtered_results = filtered_search_results
                # links = extract_links_from_search_results()
            self.all_links_of_system.extend(final_filtered_results)

            # Final synthesis after all iterations
            self._update_progress(
                f"Synthesizing {len(final_filtered_results)} sources from {iterations_to_run} iterations...",
                90,
                {"phase": "synthesis", "type": "milestone"},
            )

            # Final synthesis
            final_citation_result = self.citation_handler.analyze_followup(
                query,
                final_filtered_results,
                previous_knowledge="",  # Empty string as we don't need previous knowledge here
                nr_of_links=total_citation_count_before_this_search,
            )

            # Add null check for final_citation_result
            if final_citation_result:
                synthesized_content = final_citation_result["content"]
                documents = final_citation_result.get("documents", [])
            else:
                synthesized_content = (
                    "No relevant results found in final synthesis."
                )
                documents = []

            # Add a final synthesis finding
            final_finding = {
                "phase": "Final synthesis",
                "content": synthesized_content,
                "question": query,
                "search_results": self.all_links_of_system,
                "documents": documents,
            }
            findings.append(final_finding)

            # Add documents to repository
            self.findings_repository.add_documents(documents)

            # Transfer questions to repository
            self.findings_repository.set_questions_by_iteration(
                self.questions_by_iteration
            )

            # Format findings
            formatted_findings = (
                self.findings_repository.format_findings_to_text(
                    findings, synthesized_content
                )
            )

        except Exception as e:
            error_msg = f"Error in research process: {e!s}"
            logger.exception(error_msg)
            synthesized_content = f"Error: {e!s}"
            formatted_findings = f"Error: {e!s}"
            finding = {
                "phase": "Error",
                "content": synthesized_content,
                "question": query,
                "search_results": [],
                "documents": [],
            }
            findings.append(finding)

        # Note: "Research complete" progress is handled by research_service after strategy returns

        return {
            "findings": findings,
            "iterations": iterations_to_run,
            "questions_by_iteration": self.questions_by_iteration,
            "formatted_findings": formatted_findings,
            "current_knowledge": synthesized_content,
            "all_links_of_system": self.all_links_of_system,
        }
