"""
Oracle Agent - RLM-powered codebase knowledge system

The Oracle has unlimited context and knows everything about the codebase.

Uses hybrid retrieval for intelligent context selection:
- BM25 keyword search for relevant code
- Code graph traversal for related components
- Hierarchical summaries for context expansion
"""

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional

from groknroll.core.rlm import RLM
from groknroll.oracle.codebase_indexer import CodebaseIndexer
from groknroll.storage.database import Database

if TYPE_CHECKING:
    from groknroll.oracle.hybrid_retriever import HybridRetriever


@dataclass
class OracleResponse:
    """Response from the Oracle"""

    question: str
    answer: str
    confidence: str  # high, medium, low
    sources: list[str]  # Files referenced
    execution_time: float

    def __str__(self):
        return f"""
Oracle Response
{"=" * 80}
Question: {self.question}

Answer:
{self.answer}

Confidence: {self.confidence}
Sources: {", ".join(self.sources[:5])}{"..." if len(self.sources) > 5 else ""}
Time: {self.execution_time:.2f}s
{"=" * 80}
"""


class OracleAgent:
    """
    Oracle Agent - Knows everything about the codebase

    Uses RLM's unlimited context to answer questions about:
    - Code structure and architecture
    - Function and class locations
    - Import dependencies
    - Implementation details
    - Best practices in the codebase
    - How to use features
    - Where bugs might be
    - Refactoring suggestions

    Example:
        oracle = OracleAgent("/path/to/project")
        response = oracle.ask("Where is the RLM class defined?")
        print(response.answer)
    """

    def __init__(
        self,
        project_path: Path,
        backend: str = "openai",
        model: str = "gpt-4o-mini",
        verbose: bool = False,
        use_hybrid_retrieval: bool = True,
        summary_backend: str = "anthropic",
        summary_model: str = "claude-sonnet-4-20250514",
        database: Optional[Database] = None,
    ):
        """
        Initialize Oracle Agent

        Args:
            project_path: Path to the project root
            backend: LLM backend to use for queries
            model: Model name for queries
            verbose: Whether to show detailed output
            use_hybrid_retrieval: Whether to use hybrid retrieval (BM25 + graph + summaries)
            summary_backend: LLM backend for summary generation
            summary_model: Model for summary generation
            database: Database instance (creates default if None)
        """
        self.project_path = Path(project_path).resolve()
        self.verbose = verbose
        self.use_hybrid_retrieval = use_hybrid_retrieval

        print(f"🔮 Initializing Oracle Agent for: {self.project_path}")

        # Initialize database
        self.database = database or Database()
        self.project = self.database.get_or_create_project(self.project_path)

        # Build codebase index with optional hybrid retrieval
        self.indexer = CodebaseIndexer(
            self.project_path,
            database=self.database,
            project_id=self.project.id,
        )

        if use_hybrid_retrieval:
            print("📊 Building hybrid index (this may take a while for large codebases)...")
            self.index = self.indexer.build_hybrid_index(
                include_content=True,
                generate_summaries=True,
                build_bm25=True,
                build_graph=True,
                backend=summary_backend,
                model=summary_model,
                verbose=verbose,
            )
            self.retriever: Optional["HybridRetriever"] = self.indexer.get_hybrid_retriever(
                verbose=verbose
            )
        else:
            self.index = self.indexer.build_index(include_content=True)
            self.retriever = None

        # Initialize RLM
        self.rlm = RLM(
            backend=backend,
            backend_kwargs={"model_name": model},
            environment="local",
            max_iterations=10,
            verbose=verbose,
            database=self.database,
            project_id=self.project.id,
        )

        print(f"✅ Oracle ready - {self.index.total_files} files indexed")

    def ask(self, question: str, context: Optional[dict[str, Any]] = None) -> OracleResponse:
        """
        Ask the Oracle a question about the codebase

        Args:
            question: Question about the codebase
            context: Additional context to provide

        Returns:
            OracleResponse with answer and metadata
        """
        print(f"\n🔮 Oracle processing: {question[:80]}...")

        # Build comprehensive context for RLM
        rlm_context = self._build_context(question, context)

        # Ask RLM
        import time

        start = time.time()

        result = self.rlm.completion(rlm_context)

        execution_time = time.time() - start

        # Extract sources from response
        sources = self._extract_sources(result.response)

        return OracleResponse(
            question=question,
            answer=result.response,
            confidence="high",  # Could be inferred from response
            sources=sources,
            execution_time=execution_time,
        )

    def _build_context(self, question: str, extra_context: Optional[dict] = None) -> str:
        """Build comprehensive context for RLM"""

        parts = [
            "You are the Oracle - an all-knowing agent with complete knowledge of this codebase.",
            "You have unlimited context and can examine any file in detail.",
            "",
            "# Codebase Overview",
            self.indexer.get_summary(),
            "",
            "# Available Files",
        ]

        # Add file listing
        for relative_path in sorted(self.index.files.keys())[:100]:  # Top 100 files
            file_info = self.index.files[relative_path]
            parts.append(f"- {relative_path} ({file_info.language}, {file_info.lines} lines)")

        parts.append("")
        parts.append("# Question")
        parts.append(question)
        parts.append("")

        # Add relevant context based on question
        parts.append("# Relevant Code")
        parts.append("")

        # Try to find relevant files
        relevant_files = self._find_relevant_files(question)

        for file_path in relevant_files[:5]:  # Top 5 most relevant
            file_info = self.index.files.get(file_path)
            if file_info and file_info.content:
                parts.append(f"## File: {file_path}")
                parts.append(f"```{file_info.language}")
                parts.append(file_info.content)
                parts.append("```")
                parts.append("")

        if extra_context:
            parts.append("# Additional Context")
            for key, value in extra_context.items():
                parts.append(f"{key}: {value}")
            parts.append("")

        parts.append("# Instructions")
        parts.append("Answer the question comprehensively using your knowledge of the codebase.")
        parts.append("Reference specific files, functions, and classes when relevant.")
        parts.append("Provide code examples if helpful.")
        parts.append("Use FINAL_VAR(str(your_answer)) to return your answer.")

        return "\n".join(parts)

    def _find_relevant_files(self, question: str) -> list[str]:
        """
        Find files relevant to the question.

        Uses hybrid retrieval (BM25 + graph + hierarchy) when available,
        falls back to keyword matching otherwise.
        """
        # Use hybrid retrieval if available
        if self.retriever is not None:
            results = self.retriever.retrieve(
                question,
                max_results=10,
                include_content=False,  # Content loaded separately
                scope_types=["module", "class", "function"],
            )

            # Extract unique file paths from results
            seen = set()
            relevant = []
            for result in results:
                # For class/function results, use the scope_path (file path)
                file_path = result.scope_path
                if file_path not in seen:
                    seen.add(file_path)
                    relevant.append(file_path)

            if relevant:
                if self.verbose:
                    print(f"   Hybrid retrieval found {len(relevant)} relevant files")
                return relevant

        # Fallback: keyword-based matching
        return self._keyword_based_search(question)

    def _keyword_based_search(self, question: str) -> list[str]:
        """
        Legacy keyword-based file search.

        Used as fallback when hybrid retrieval is not available.
        """
        relevant = []
        question_lower = question.lower()

        # Check for specific file mentions
        for file_path in self.index.files.keys():
            file_name = Path(file_path).name.lower()
            if file_name in question_lower or file_path in question_lower:
                relevant.append(file_path)

        # Check for class/function mentions
        for class_name, file_paths in self.index.classes.items():
            if class_name.lower() in question_lower:
                relevant.extend(file_paths)

        for func_name, file_paths in self.index.functions.items():
            if func_name.lower() in question_lower:
                relevant.extend(file_paths)

        # Keywords that suggest core files
        if any(kw in question_lower for kw in ["main", "core", "base", "init"]):
            for file_path in self.index.files.keys():
                if any(name in file_path for name in ["__init__.py", "main.py", "core/", "rlm.py"]):
                    relevant.append(file_path)

        # Remove duplicates while preserving order
        seen = set()
        unique_relevant = []
        for item in relevant:
            if item not in seen:
                seen.add(item)
                unique_relevant.append(item)

        return unique_relevant if unique_relevant else list(self.index.files.keys())[:10]

    def _extract_sources(self, response: str) -> list[str]:
        """Extract file references from response"""
        sources = set()

        for file_path in self.index.files.keys():
            if file_path in response or Path(file_path).name in response:
                sources.add(file_path)

        return list(sources)

    def find_function(self, function_name: str) -> OracleResponse:
        """Ask Oracle where a function is defined"""
        return self.ask(f"Where is the function '{function_name}' defined? Show me the code.")

    def find_class(self, class_name: str) -> OracleResponse:
        """Ask Oracle where a class is defined"""
        return self.ask(
            f"Where is the class '{class_name}' defined? Show me the code and explain what it does."
        )

    def explain_file(self, file_path: str) -> OracleResponse:
        """Ask Oracle to explain a file"""
        return self.ask(
            f"Explain what {file_path} does and how it fits into the overall architecture."
        )

    def find_usage(self, item_name: str) -> OracleResponse:
        """Ask Oracle how something is used"""
        return self.ask(f"Show me examples of how '{item_name}' is used in the codebase.")

    def suggest_refactoring(self, description: str) -> OracleResponse:
        """Ask Oracle for refactoring suggestions"""
        return self.ask(f"Suggest how to refactor: {description}")

    def find_bugs(self, area: Optional[str] = None) -> OracleResponse:
        """Ask Oracle to identify potential bugs"""
        question = (
            f"Identify potential bugs in {area}"
            if area
            else "Identify potential bugs in the codebase"
        )
        return self.ask(question)

    def get_architecture_overview(self) -> OracleResponse:
        """Ask Oracle for architecture overview"""
        return self.ask(
            "Provide a comprehensive overview of the codebase architecture. "
            "Explain the main components, how they interact, and the overall design pattern."
        )

    def how_to_add_feature(self, feature: str) -> OracleResponse:
        """Ask Oracle how to add a feature"""
        return self.ask(
            f"How would I add this feature: {feature}? "
            f"Which files would I need to modify? Show me the steps."
        )

    def trace_execution(self, entry_point: str) -> OracleResponse:
        """Ask Oracle to trace execution flow"""
        return self.ask(
            f"Trace the execution flow starting from {entry_point}. "
            f"Show me the call chain and what happens step by step."
        )

    def explain_imports(self, module: str) -> OracleResponse:
        """Ask Oracle about import relationships"""
        return self.ask(
            f"Explain the import relationships for {module}. Which files import it and why?"
        )

    def index_codebase(
        self,
        generate_summaries: bool = True,
        force_rescan: bool = False,
    ) -> None:
        """
        Re-index the codebase.

        Useful for updating the index after code changes.

        Args:
            generate_summaries: Whether to regenerate LLM summaries
            force_rescan: Whether to force full rescan (ignore cache)
        """
        print(f"🔄 Re-indexing codebase at: {self.project_path}")

        if force_rescan:
            # Clear file cache
            self.indexer._file_cache.clear()

        if self.use_hybrid_retrieval:
            self.index = self.indexer.build_hybrid_index(
                include_content=True,
                generate_summaries=generate_summaries,
                build_bm25=True,
                build_graph=True,
                verbose=self.verbose,
            )
            self.retriever = self.indexer.get_hybrid_retriever(verbose=self.verbose)
        else:
            self.index = self.indexer.build_index(include_content=True)

        # Update project stats
        self.database.update_project_stats(
            self.project.id,
            self.index.total_files,
            self.index.total_lines,
        )

        print(f"✅ Re-indexed {self.index.total_files} files")

    def get_retrieval_stats(self) -> dict[str, Any]:
        """
        Get statistics about the hybrid retrieval system.

        Returns:
            Dict with index statistics
        """
        stats = {
            "total_files": self.index.total_files,
            "total_lines": self.index.total_lines,
            "total_functions": len(self.index.functions),
            "total_classes": len(self.index.classes),
            "hybrid_retrieval_enabled": self.use_hybrid_retrieval,
        }

        if self.use_hybrid_retrieval and self.retriever:
            stats["bm25_documents"] = self.database.get_total_documents(self.project.id)

        return stats
