"""
BrowseComp Entity-Focused Search Strategy

This strategy is specifically designed for BrowseComp questions that require finding
specific entities (companies, people, events) that match multiple constraints.

Key features:
1. Entity extraction and progressive search
2. Knowledge graph building approach
3. Multi-constraint verification with caching
4. Specialized search patterns for different entity types
"""

import asyncio
import json
import re
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List, Tuple

from loguru import logger

from ...database.thread_local_session import thread_cleanup
from ...utilities.json_utils import extract_json, get_llm_response_text
from ...utilities.search_cache import get_search_cache
from ..candidate_exploration import ConstraintGuidedExplorer
from ..constraint_checking import DualConfidenceChecker
from ..constraints import Constraint, ConstraintAnalyzer
from ..questions import BrowseCompQuestionGenerator
from .base_strategy import BaseSearchStrategy


@dataclass
class EntityCandidate:
    """Enhanced candidate with entity-specific metadata."""

    name: str
    entity_type: str  # company, person, event, etc.
    aliases: List[str] = None
    properties: Dict[str, any] = None
    sources: List[str] = None
    confidence: float = 0.0
    constraint_matches: Dict[str, float] = None

    def __post_init__(self):
        self.aliases = self.aliases or []
        self.properties = self.properties or {}
        self.sources = self.sources or []
        self.constraint_matches = self.constraint_matches or {}


MAX_LOCAL_CACHE = 200  # Cap in-memory search cache to prevent unbounded growth


class EntityKnowledgeGraph:
    """Build and maintain knowledge about discovered entities."""

    def __init__(self):
        self.entities = {}  # name -> EntityCandidate
        self.constraint_evidence = defaultdict(
            dict
        )  # constraint -> entity -> evidence
        self.search_cache = {}  # query -> results

    def add_entity(self, entity: EntityCandidate):
        """Add or update an entity in the knowledge graph."""
        if entity.name in self.entities:
            # Merge information
            existing = self.entities[entity.name]
            existing.aliases.extend(entity.aliases)
            existing.aliases = list(set(existing.aliases))
            existing.properties.update(entity.properties)
            existing.sources.extend(entity.sources)
            existing.sources = list(set(existing.sources))
            existing.constraint_matches.update(entity.constraint_matches)
        else:
            self.entities[entity.name] = entity

    def add_constraint_evidence(
        self, constraint: str, entity_name: str, evidence: Dict
    ):
        """Add evidence for a constraint-entity pair."""
        self.constraint_evidence[constraint][entity_name] = evidence

    def get_entities_by_constraint(
        self, constraint: str, min_confidence: float = 0.5
    ) -> List[EntityCandidate]:
        """Get entities that match a constraint above confidence threshold."""
        matching = []
        for entity in self.entities.values():
            if constraint in entity.constraint_matches:
                if entity.constraint_matches[constraint] >= min_confidence:
                    matching.append(entity)
        return sorted(
            matching,
            key=lambda e: e.constraint_matches.get(constraint, 0),
            reverse=True,
        )


class BrowseCompEntityStrategy(BaseSearchStrategy):
    """
    Entity-focused search strategy for BrowseComp questions.

    This strategy:
    1. Extracts key entities from the query
    2. Performs broad entity discovery searches
    3. Builds a knowledge graph of candidates
    4. Progressively verifies constraints
    5. Uses caching to avoid redundant searches
    """

    def __init__(
        self,
        model,
        search,
        all_links_of_system=None,
        settings_snapshot=None,
        **kwargs,
    ):
        super().__init__(
            all_links_of_system=all_links_of_system,
            settings_snapshot=settings_snapshot,
        )

        # Store model and search engine
        self.model = model
        self.search_engine = search

        # Initialize components that depend on model/search
        if self.model:
            self.constraint_analyzer = ConstraintAnalyzer(model=self.model)
            self.question_generator = BrowseCompQuestionGenerator(
                model=self.model
            )
        else:
            logger.warning("No model provided to BrowseCompEntityStrategy")

        self.knowledge_graph = EntityKnowledgeGraph()

        # Initialize constraint checker with entity-aware settings
        if self.model:
            self.constraint_checker = DualConfidenceChecker(
                model=self.model,
                evidence_gatherer=self._gather_entity_evidence,
                negative_threshold=0.3,  # More lenient for entities
                positive_threshold=0.4,
                uncertainty_penalty=0.1,
                negative_weight=1.0,
            )

        # Initialize specialized explorer
        if self.search_engine and self.model:
            self.explorer = ConstraintGuidedExplorer(
                search_engine=self.search_engine, model=self.model
            )

        # Entity type patterns
        self.entity_patterns = {
            "company": [
                "company",
                "corporation",
                "group",
                "firm",
                "business",
                "conglomerate",
            ],
            "person": ["person", "individual", "character", "figure", "people"],
            "event": [
                "event",
                "incident",
                "occurrence",
                "game",
                "match",
                "competition",
            ],
            "location": [
                "place",
                "location",
                "city",
                "country",
                "region",
                "area",
            ],
            "product": ["product", "item", "device", "software", "app", "tool"],
        }

    async def search(
        self,
        query: str,
        search_engines: List[str] = None,
        progress_callback=None,
        **kwargs,
    ) -> Tuple[str, Dict]:
        """Execute entity-focused search strategy."""
        try:
            logger.info(f"🎯 Starting BrowseComp Entity Search for: {query}")

            # Phase 1: Constraint and entity analysis
            if progress_callback:
                progress_callback(
                    {
                        "phase": "entity_analysis",
                        "progress": 10,
                        "message": "Analyzing query for entities and constraints",
                    }
                )

            constraints = self.constraint_analyzer.extract_constraints(query)
            entity_type = self._identify_entity_type(query)
            logger.info(
                f"Identified entity type: {entity_type}, {len(constraints)} constraints"
            )

            # Phase 2: Initial entity discovery
            if progress_callback:
                progress_callback(
                    {
                        "phase": "entity_discovery",
                        "progress": 25,
                        "message": f"Searching for {entity_type} entities",
                    }
                )

            initial_entities = await self._discover_entities(
                query,
                entity_type,
                constraints[:2],  # Use first 2 constraints for initial search
            )
            logger.info(f"Discovered {len(initial_entities)} initial entities")

            # Phase 3: Progressive constraint verification
            best_candidate = None
            iteration = 0
            max_iterations = 10

            while iteration < max_iterations:
                iteration += 1

                if progress_callback:
                    progress_callback(
                        {
                            "phase": "constraint_verification",
                            "progress": 25 + (iteration * 50 / max_iterations),
                            "message": f"Verifying constraints (iteration {iteration}/{max_iterations})",
                        }
                    )

                # Generate targeted searches based on current knowledge
                questions = self.question_generator.generate_questions(
                    current_knowledge=self._summarize_knowledge(),
                    query=query,
                    questions_per_iteration=5,
                    iteration=iteration,
                )

                # Search for evidence
                new_entities = await self._search_with_questions(
                    questions, entity_type
                )

                # Add to knowledge graph
                for entity in new_entities:
                    self.knowledge_graph.add_entity(entity)

                # Evaluate all entities against constraints
                evaluated = await self._evaluate_entities(constraints)

                # Check for high-confidence matches
                if evaluated:
                    best_candidate = evaluated[0]
                    if best_candidate.confidence > 0.8:
                        logger.info(
                            f"✅ Found high-confidence match: {best_candidate.name} ({best_candidate.confidence:.2%})"
                        )
                        break

                # Early stopping if no progress
                if iteration > 3 and not self.knowledge_graph.entities:
                    logger.warning(
                        "No entities found after 3 iterations, stopping"
                    )
                    break

            # Phase 4: Generate final answer
            if progress_callback:
                progress_callback(
                    {
                        "phase": "answer_generation",
                        "progress": 90,
                        "message": "Generating final answer",
                    }
                )

            if best_candidate and best_candidate.confidence > 0.5:
                answer = await self._generate_entity_answer(
                    query, best_candidate, constraints
                )
            else:
                answer = await self._generate_uncertain_answer(
                    query, evaluated[:3] if evaluated else []
                )

            # Prepare metadata
            metadata = {
                "strategy": "browsecomp_entity",
                "entity_type": entity_type,
                "entities_discovered": len(self.knowledge_graph.entities),
                "iterations": iteration,
                "best_candidate": best_candidate.name
                if best_candidate
                else None,
                "confidence": best_candidate.confidence
                if best_candidate
                else 0.0,
                "constraint_count": len(constraints),
                "cached_searches": len(self.knowledge_graph.search_cache),
            }

            return answer, metadata

        except Exception:
            logger.exception("Error in BrowseComp entity search")
            return "Search failed", {"error": "Search failed"}

    def _identify_entity_type(self, query: str) -> str:
        """Identify what type of entity we're looking for."""
        query_lower = query.lower()

        for entity_type, keywords in self.entity_patterns.items():
            if any(keyword in query_lower for keyword in keywords):
                return entity_type

        # Default based on common patterns
        if "who" in query_lower:
            return "person"
        if "which" in query_lower:
            return "product"
        if "what" in query_lower and "company" in query_lower:
            return "company"
        return "entity"

    async def _discover_entities(
        self,
        query: str,
        entity_type: str,
        initial_constraints: List[Constraint],
    ) -> List[EntityCandidate]:
        """Discover initial entity candidates."""
        entities = []

        # Generate entity-focused search queries
        search_queries = self._generate_entity_searches(
            entity_type, initial_constraints
        )

        # Execute searches in parallel
        search_tasks = []
        for search_query in search_queries[:5]:  # Limit initial searches
            if search_query not in self.knowledge_graph.search_cache:
                search_tasks.append(self._cached_search(search_query))

        results = await asyncio.gather(*search_tasks)

        # Extract entities from results
        for query_results in results:
            extracted = await self._extract_entities_from_results(
                query_results, entity_type
            )
            entities.extend(extracted)

        return entities

    def _generate_entity_searches(
        self, entity_type: str, constraints: List[Constraint]
    ) -> List[str]:
        """Generate search queries for entity discovery."""
        searches = []

        # Type-specific base queries
        if entity_type == "company":
            searches.extend(
                [
                    "largest companies conglomerates groups",
                    "major corporation multinational business",
                    "company group founded",
                ]
            )
        elif entity_type == "person":
            searches.extend(
                [
                    "famous people individuals",
                    "notable person character",
                    "who known for",
                ]
            )
        elif entity_type == "event":
            searches.extend(
                [
                    "major events competitions",
                    "historical event game match",
                    "significant occurrence",
                ]
            )

        # Add constraint-based searches
        for constraint in constraints:
            if constraint.type.value == "TEMPORAL":
                # Extract years/dates
                years = re.findall(r"\b(19\d{2}|20\d{2})\b", constraint.value)
                for year in years:
                    searches.append(f"{entity_type} {year}")
            elif constraint.type.value == "LOCATION":
                # Extract location names
                locations = re.findall(
                    r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b", constraint.value
                )
                for location in locations:
                    searches.append(f"{entity_type} {location}")
            elif constraint.type.value == "STATISTIC":
                # Extract numbers
                numbers = re.findall(r"\b\d+\b", constraint.value)
                for number in numbers:
                    searches.append(f"{entity_type} {number}")

        return searches

    async def _extract_entities_from_results(
        self, results: List[Dict], entity_type: str
    ) -> List[EntityCandidate]:
        """Extract entity candidates from search results."""
        if not results:
            return []

        # Use LLM to extract entities
        results_text = "\n".join(
            [
                f"- {r.get('title', '')}: {r.get('snippet', '')[:200]}"
                for r in results[:10]
            ]
        )

        prompt = f"""Extract {entity_type} entities from these search results.

Search Results:
{results_text}

For each entity found, provide:
1. Name (official/full name)
2. Aliases (other names, abbreviations)
3. Key properties (founding year, location, size, etc.)

Format as JSON:
[
  {{
    "name": "Entity Name",
    "aliases": ["alias1", "alias2"],
    "properties": {{"key": "value"}}
  }}
]

Return only entities that are clearly {entity_type} entities."""

        response = await self.model.ainvoke(prompt)
        response_text = get_llm_response_text(response)
        entities_data = extract_json(response_text, expected_type=list)

        if entities_data is None:
            logger.warning("Failed to parse entity extraction response")
            return []

        entities = []
        for data in entities_data:
            entity = EntityCandidate(
                name=data["name"],
                entity_type=entity_type,
                aliases=data.get("aliases", []),
                properties=data.get("properties", {}),
                sources=[r.get("url", "") for r in results[:3]],
            )
            entities.append(entity)

        return entities

    async def _search_with_questions(
        self, questions: List[str], entity_type: str
    ) -> List[EntityCandidate]:
        """Search using generated questions and extract entities."""
        all_entities = []

        # Execute searches
        search_tasks = []
        for question in questions:
            if question not in self.knowledge_graph.search_cache:
                search_tasks.append(self._cached_search(question))

        results = await asyncio.gather(*search_tasks)

        # Extract entities
        for query_results in results:
            entities = await self._extract_entities_from_results(
                query_results, entity_type
            )
            all_entities.extend(entities)

        return all_entities

    async def _evaluate_entities(
        self, constraints: List[Constraint]
    ) -> List[EntityCandidate]:
        """Evaluate all entities against constraints."""
        evaluated = []

        for entity_name, entity in self.knowledge_graph.entities.items():
            # Check each constraint
            total_score = 0.0
            constraint_scores = {}

            for constraint in constraints:
                # Check if we already have evidence for this constraint-entity pair
                if constraint.value in self.knowledge_graph.constraint_evidence:
                    if (
                        entity_name
                        in self.knowledge_graph.constraint_evidence[
                            constraint.value
                        ]
                    ):
                        evidence = self.knowledge_graph.constraint_evidence[
                            constraint.value
                        ][entity_name]
                        score = evidence.get("score", 0.0)
                    else:
                        # Gather new evidence
                        score = await self._verify_entity_constraint(
                            entity, constraint
                        )
                else:
                    score = await self._verify_entity_constraint(
                        entity, constraint
                    )

                constraint_scores[constraint.value] = score
                total_score += score * constraint.weight

            # Update entity with scores
            entity.constraint_matches = constraint_scores
            entity.confidence = total_score / sum(c.weight for c in constraints)

            if entity.confidence > 0.3:  # Only keep reasonable candidates
                evaluated.append(entity)

        # Sort by confidence
        return sorted(evaluated, key=lambda e: e.confidence, reverse=True)

    async def _verify_entity_constraint(
        self, entity: EntityCandidate, constraint: Constraint
    ) -> float:
        """Verify if an entity satisfies a constraint."""
        # Build targeted search query
        search_terms = [entity.name] + entity.aliases[:2]
        constraint_terms = self._extract_constraint_terms(constraint)

        best_score = 0.0
        for term in search_terms:
            query = f'"{term}" {" ".join(constraint_terms)}'

            # Search for evidence
            results = await self._cached_search(query)

            if results:
                # Quick verification with LLM
                evidence_text = " ".join(
                    [r.get("snippet", "") for r in results[:3]]
                )

                prompt = f"""Does {entity.name} satisfy this constraint?

Constraint: {constraint.description}
Evidence: {evidence_text}

Answer with a confidence score from 0.0 to 1.0 and brief explanation.
Format: SCORE: X.X | REASON: explanation"""

                response = await self.model.ainvoke(prompt)
                content = response.content

                # Extract score
                score_match = re.search(r"SCORE:\s*([\d.]+)", content)
                if score_match:
                    score = float(score_match.group(1))
                    best_score = max(best_score, score)

                    # Cache the evidence
                    self.knowledge_graph.add_constraint_evidence(
                        constraint.value,
                        entity.name,
                        {
                            "score": score,
                            "evidence": evidence_text,
                            "reason": content,
                        },
                    )

        return best_score

    def _extract_constraint_terms(self, constraint: Constraint) -> List[str]:
        """Extract searchable terms from a constraint."""
        terms = []

        # Remove common prefixes
        value = constraint.value
        for prefix in ["The answer must", "Must be", "Should be", "Is"]:
            if value.startswith(prefix):
                value = value[len(prefix) :].strip()
                break

        # Extract specific terms based on constraint type
        if constraint.type.value == "TEMPORAL":
            # Extract years
            terms.extend(re.findall(r"\b(19\d{2}|20\d{2})\b", value))
        elif constraint.type.value == "STATISTIC":
            # Extract numbers
            terms.extend(re.findall(r"\b\d+\b", value))
        elif constraint.type.value == "LOCATION":
            # Extract proper nouns
            terms.extend(
                re.findall(r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b", value)
            )

        # Add key descriptive words
        words = value.split()
        for word in words:
            if len(word) > 4 and word.lower() not in [
                "must",
                "should",
                "would",
                "could",
            ]:
                terms.append(word)

        return terms[:5]  # Limit to avoid overly long queries

    def extract_entity_candidates(
        self, constraints: List[Constraint]
    ) -> List[str]:
        """
        Extract potential entity names using constraint analysis.
        Implements progressive entity discovery from improvement strategy.
        """
        candidates = []

        for constraint in constraints:
            # Look for proper nouns (likely entity names)
            proper_nouns = re.findall(
                r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b", constraint.value
            )
            candidates.extend(proper_nouns)

            # Look for company name patterns
            company_patterns = [
                r"([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\s+(?:Group|Inc|Ltd|Corp|Company|Corporation)",
                r"([A-Z][a-z]+(?:-[A-Z][a-z]+)*)\s+(?:Group|Inc|Ltd|Corp)",
                r"([A-Z]{2,}(?:-[A-Z]{2,})*)",  # Acronyms like PRAN-RFL
            ]

            for pattern in company_patterns:
                matches = re.findall(pattern, constraint.value)
                candidates.extend(matches)

        # Remove duplicates and sort by specificity
        unique_candidates = list(set(candidates))
        return sorted(
            unique_candidates, key=self.entity_specificity, reverse=True
        )

    def entity_specificity(self, entity: str) -> float:
        """
        Score entity specificity for search prioritization.
        Higher scores = more specific entities to search first.
        """
        score = 0.0

        # Longer names are typically more specific
        score += len(entity) * 0.1

        # Multiple words indicate more specificity
        word_count = len(entity.split())
        score += word_count * 2.0

        # Company suffixes indicate high specificity
        company_suffixes = [
            "Group",
            "Inc",
            "Ltd",
            "Corp",
            "Corporation",
            "Company",
            "Conglomerate",
        ]
        if any(suffix in entity for suffix in company_suffixes):
            score += 10.0

        # Hyphenated names (like PRAN-RFL) are often specific
        if "-" in entity:
            score += 5.0

        # All caps acronyms are specific
        if entity.isupper() and len(entity) >= 3:
            score += 8.0

        return score

    def _gather_entity_evidence(self, candidate, constraint):
        """Evidence gatherer function for constraint checker."""
        # Convert to EntityCandidate if needed
        if not isinstance(candidate, EntityCandidate):
            entity = EntityCandidate(
                name=candidate.name
                if hasattr(candidate, "name")
                else str(candidate),
                entity_type="unknown",
            )
        else:
            entity = candidate

        # Check cache first
        if constraint.value in self.knowledge_graph.constraint_evidence:
            if (
                entity.name
                in self.knowledge_graph.constraint_evidence[constraint.value]
            ):
                evidence_data = self.knowledge_graph.constraint_evidence[
                    constraint.value
                ][entity.name]
                return [
                    {
                        "text": evidence_data.get("evidence", ""),
                        "source": "cache",
                        "confidence": evidence_data.get("score", 0.5),
                    }
                ]

        # Generate search query
        constraint_terms = self._extract_constraint_terms(constraint)
        query = f'"{entity.name}" {" ".join(constraint_terms)}'

        # Search
        results = (
            self.search_engine.run(query)
            if hasattr(self.search_engine, "run")
            else []
        )

        # Convert to evidence format
        evidence = []
        for i, result in enumerate(results[:3]):
            evidence.append(
                {
                    "text": result.get("snippet", ""),
                    "source": result.get("url", f"result_{i}"),
                    "confidence": 0.7 - (i * 0.1),
                }
            )

        return evidence

    def _summarize_knowledge(self) -> str:
        """Summarize current knowledge for question generation."""
        summary_parts = []

        # Top entities by confidence
        entities_by_confidence = sorted(
            self.knowledge_graph.entities.values(),
            key=lambda e: e.confidence,
            reverse=True,
        )[:5]

        if entities_by_confidence:
            summary_parts.append("Top candidates found:")
            for entity in entities_by_confidence:
                summary_parts.append(
                    f"- {entity.name} ({entity.entity_type}): {entity.confidence:.2%} confidence"
                )
                if entity.properties:
                    props = ", ".join(
                        f"{k}={v}"
                        for k, v in list(entity.properties.items())[:3]
                    )
                    summary_parts.append(f"  Properties: {props}")

        # Constraint satisfaction summary
        if self.knowledge_graph.constraint_evidence:
            summary_parts.append("\nConstraint verification status:")
            for constraint, entities in list(
                self.knowledge_graph.constraint_evidence.items()
            )[:3]:
                summary_parts.append(f"- {constraint[:50]}...")
                for entity_name, evidence in list(entities.items())[:2]:
                    score = evidence.get("score", 0)
                    summary_parts.append(f"  {entity_name}: {score:.2%}")

        return "\n".join(summary_parts)

    async def _cached_search(self, query: str) -> List[Dict]:
        """Perform search with caching support."""
        cache = get_search_cache()

        # Check cache first
        cached_results = cache.get(query, "browsecomp_entity")
        if cached_results is not None:
            logger.debug(f"Using cached search results for: {query[:50]}...")
            return cached_results

        # Perform actual search
        try:
            if hasattr(self.search_engine, "run"):
                results = self.search_engine.run(query)
            elif hasattr(self.search_engine, "search"):
                results = self.search_engine.search(query)
            elif callable(self.search_engine):
                results = self.search_engine(query)
            else:
                logger.warning("Search engine has no callable method")
                return []

            # Normalize results format
            if isinstance(results, list):
                normalized_results = results
            elif isinstance(results, dict):
                normalized_results = results.get("results", [])
            else:
                normalized_results = []

            # Cache the results
            cache.put(
                query, normalized_results, "browsecomp_entity", ttl=1800
            )  # 30 minutes

            # Also populate the in-memory search_cache (with size cap)
            if len(self.knowledge_graph.search_cache) >= MAX_LOCAL_CACHE:
                try:
                    oldest_key = next(iter(self.knowledge_graph.search_cache))
                    del self.knowledge_graph.search_cache[oldest_key]
                except (StopIteration, KeyError):
                    pass
            self.knowledge_graph.search_cache[query] = normalized_results

            logger.debug(f"Cached new search results for: {query[:50]}...")
            return normalized_results

        except Exception:
            logger.exception(f"Search failed for query '{query}'")
            return []

    async def _generate_entity_answer(
        self,
        query: str,
        best_entity: EntityCandidate,
        constraints: List[Constraint],
    ) -> str:
        """Generate answer for the best matching entity."""
        constraint_details = []
        for constraint in constraints:
            score = best_entity.constraint_matches.get(constraint.value, 0)
            constraint_details.append(
                f"- {constraint.description}: {score:.2%} confidence"
            )

        prompt = f"""Based on the search results, provide the answer to: {query}

Best matching {best_entity.entity_type}: {best_entity.name}
Overall confidence: {best_entity.confidence:.2%}

Aliases/Other names: {", ".join(best_entity.aliases[:3]) if best_entity.aliases else "None found"}

Properties:
{json.dumps(best_entity.properties, indent=2) if best_entity.properties else "No properties found"}

Constraint satisfaction:
{chr(10).join(constraint_details)}

Provide a clear, confident answer that explains why this entity matches the constraints."""

        response = await self.model.ainvoke(prompt)
        return response.content

    async def _generate_uncertain_answer(
        self, query: str, top_entities: List[EntityCandidate]
    ) -> str:
        """Generate answer when no high-confidence match is found."""
        if not top_entities:
            return "Unable to find any entities matching the specified constraints."

        candidates_info = []
        for entity in top_entities:
            candidates_info.append(
                f"- {entity.name}: {entity.confidence:.2%} confidence"
            )

        prompt = f"""Based on the search results for: {query}

Found these potential matches but with low confidence:
{chr(10).join(candidates_info)}

The search was unable to find a definitive answer matching all constraints.

Provide a helpful response explaining what was found and why no definitive answer could be determined."""

        response = await self.model.ainvoke(prompt)
        return response.content

    def analyze_topic(self, query: str) -> Dict:
        """
        Analyze a topic using entity-focused BrowseComp approach.

        Args:
            query: The research query to analyze

        Returns:
            Dict containing findings, iterations, and questions
        """
        import asyncio

        try:
            # Run async search — detect whether we're inside an event loop
            try:
                asyncio.get_running_loop()
                # Inside a running loop — run in a thread to avoid nesting
                import concurrent.futures

                with concurrent.futures.ThreadPoolExecutor() as executor:
                    future = executor.submit(
                        thread_cleanup(
                            lambda: asyncio.run(
                                self._analyze_topic_async(query)
                            )
                        )
                    )
                    return future.result()
            except RuntimeError:
                # No running event loop — safe to use asyncio.run() directly
                return asyncio.run(self._analyze_topic_async(query))

        except Exception as e:
            logger.exception("Error in analyze_topic")
            return {
                "findings": [f"Error analyzing query: {e!s}"],
                "iterations": 0,
                "questions": {},
                "entities_found": 0,
                "confidence": 0.0,
            }

    async def _analyze_topic_async(self, query: str) -> Dict:
        """Async implementation of topic analysis."""
        try:
            self._update_progress("Starting entity-focused analysis...", 0)

            # Parse constraints from query
            constraint_analyzer = ConstraintAnalyzer()
            constraints = constraint_analyzer.analyze_query(query)

            self._update_progress(
                f"Identified {len(constraints)} constraints", 10
            )

            # Generate initial search questions
            question_generator = BrowseCompQuestionGenerator()
            initial_questions = question_generator.generate_questions(
                query, constraints
            )

            self._update_progress("Generated initial questions", 20)

            # Progressive entity discovery
            all_entities = []
            iteration = 0
            max_iterations = 3

            while iteration < max_iterations:
                self._update_progress(
                    f"Iteration {iteration + 1}: Discovering entities...",
                    30 + iteration * 20,
                )

                # Search for entities
                entities = await self._discover_entities_from_questions(
                    initial_questions, self._determine_entity_type(query)
                )
                all_entities.extend(entities)

                # Break if we found high-confidence entities
                if any(e.confidence > 0.8 for e in entities):
                    logger.info(
                        f"Found high-confidence entities in iteration {iteration + 1}"
                    )
                    break

                iteration += 1

            self._update_progress(
                "Evaluating entities against constraints...", 80
            )

            # Evaluate entities
            evaluated_entities = await self._evaluate_entities(constraints)

            # Generate final answer
            if evaluated_entities:
                best_entity = max(
                    evaluated_entities, key=lambda e: e.confidence
                )
                if best_entity.confidence > 0.6:
                    answer = await self._generate_entity_answer(
                        query, best_entity, constraints
                    )
                else:
                    answer = await self._generate_uncertain_answer(
                        query, evaluated_entities[:3]
                    )
            else:
                answer = (
                    "No entities were found matching the specified constraints."
                )

            self._update_progress("Analysis complete", 100)

            # Return results in expected format
            return {
                "findings": [answer],
                "iterations": iteration + 1,
                "questions": {
                    f"iteration_{i}": initial_questions
                    for i in range(iteration + 1)
                },
                "entities_found": len(evaluated_entities),
                "confidence": best_entity.confidence
                if evaluated_entities
                else 0.0,
                "strategy": "browsecomp_entity",
            }

        except Exception as e:
            logger.exception("Error in async topic analysis")
            return {
                "findings": [f"Analysis failed: {e!s}"],
                "iterations": 0,
                "questions": {},
                "entities_found": 0,
                "confidence": 0.0,
            }
