"""
Search Agent - Local documentation search using ripgrep

WO-ECHO-SEARCH-AGENT-REAL-01: Real implementation for searching local docs mirror.

Searches documentation files in data/docs/ directory using ripgrep,
similar to CodebaseAgent but optimized for documentation files.

Supported file types: .md, .rst, .txt, .html

Usage:
    from helix_cdc.echo.agents.search_agent import SearchAgent
    from helix_cdc.echo.agents import register_agent

    register_agent(SearchAgent())
"""

import asyncio
import json
import os
import re
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

from . import (
    AgentResult,
    DispatchableAgent,
    PROJECT_ROOT,
    generate_agent_id,
)

# Default documentation root - can be overridden via HELIX_DOCS_PATH env var
DEFAULT_DOCS_ROOT = PROJECT_ROOT / "data" / "docs"


class SearchAgent(DispatchableAgent):
    """
    Agent for searching local documentation mirror.

    Uses ripgrep (rg) to search for patterns in documentation files and returns
    relevant file:line references with context snippets.

    Configuration:
        - HELIX_DOCS_PATH: Override default docs directory
        - max_results: Maximum number of results to return (default: 10)

    Safety: Only searches within configured docs directory.
    """

    @property
    def agent_type(self) -> str:
        return "search"

    def __init__(
        self,
        max_results: int = 10,
        docs_path: Optional[Path] = None,
    ):
        """
        Initialize the search agent.

        Args:
            max_results: Maximum number of search results to return
            docs_path: Override documentation directory path
        """
        self.max_results = max_results
        self._rg_available: Optional[bool] = None

        # Allow override via env var or parameter
        if docs_path:
            self.docs_root = Path(docs_path)
        elif os.environ.get("HELIX_DOCS_PATH"):
            self.docs_root = Path(os.environ["HELIX_DOCS_PATH"])
        else:
            self.docs_root = DEFAULT_DOCS_ROOT

    def available(self) -> bool:
        """
        Check if search agent is available.

        Returns True if:
        1. ripgrep is installed
        2. docs directory exists (or will be created)
        """
        if self._rg_available is None:
            try:
                result = subprocess.run(
                    ["rg", "--version"],
                    capture_output=True,
                    timeout=5,
                )
                self._rg_available = result.returncode == 0
            except Exception:
                self._rg_available = False
        return self._rg_available

    def _has_docs(self) -> bool:
        """Check if any documentation files exist."""
        if not self.docs_root.exists():
            return False

        # Check for any searchable files
        for ext in ["*.md", "*.rst", "*.txt", "*.html"]:
            if list(self.docs_root.rglob(ext)):
                return True
        return False

    def _extract_search_terms(self, query: str) -> List[str]:
        """
        Extract search terms from a natural language query.

        Optimized for documentation queries:
            "How do I use FastAPI path parameters?" -> ["FastAPI", "path", "parameters"]
            "What is asyncio.gather?" -> ["asyncio.gather", "asyncio", "gather"]
            "Python type hints guide" -> ["type", "hints"]
        """
        # Remove common question words
        stop_words = {
            "what", "where", "how", "which", "is", "the", "a", "an", "in", "of",
            "does", "do", "can", "use", "using", "this", "that", "are", "to",
            "about", "for", "with", "from", "be", "have", "has", "had", "will",
            "would", "could", "should", "may", "might", "must", "shall", "need",
            "want", "like", "tell", "me", "show", "explain", "describe", "give",
            "get", "make", "find", "look", "up", "documentation", "docs", "guide",
            "tutorial", "example", "examples",
        }

        # Check for quoted strings first (exact match)
        quoted = re.findall(r'"([^"]+)"', query)
        if quoted:
            return quoted

        # Check for module.function patterns (asyncio.gather, fastapi.FastAPI)
        dotted = re.findall(r'\b([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\b', query)

        # Check for identifiers (CamelCase, snake_case)
        identifiers = re.findall(r'\b([A-Z][a-zA-Z0-9]+|[a-z_][a-z0-9_]{2,})\b', query)
        identifiers = [i for i in identifiers if i.lower() not in stop_words and len(i) > 2]

        # Combine dotted patterns with identifiers, prioritizing dotted
        terms = []
        seen = set()

        for term in dotted:
            if term not in seen:
                seen.add(term)
                terms.append(term)

        for term in identifiers:
            if term not in seen and term.lower() not in seen:
                seen.add(term.lower())
                terms.append(term)

        # If still nothing, extract significant words
        if not terms:
            words = re.findall(r'\b\w+\b', query.lower())
            terms = [w for w in words if w not in stop_words and len(w) > 2]

        return terms[:5] if terms else ["python"]  # Default fallback

    def _run_ripgrep(
        self,
        pattern: str,
        context_lines: int = 2,
    ) -> List[Dict[str, Any]]:
        """
        Run ripgrep over documentation files.

        Searches .md, .rst, .txt, .html files.
        Returns list of match dicts with file, line, content.
        """
        if not self.docs_root.exists():
            return []

        args = [
            "rg",
            "--json",
            "-i",  # Case insensitive
            "--max-count", str(self.max_results * 2),  # Get extras for filtering
            # File type filters
            "-g", "*.md",
            "-g", "*.rst",
            "-g", "*.txt",
            "-g", "*.html",
        ]

        if context_lines > 0:
            args.extend(["-C", str(context_lines)])

        args.append(pattern)
        args.append(str(self.docs_root))

        try:
            result = subprocess.run(
                args,
                capture_output=True,
                text=True,
                timeout=30,
                cwd=str(self.docs_root),
            )
        except subprocess.TimeoutExpired:
            return []
        except Exception:
            return []

        matches = []
        for line in result.stdout.split("\n"):
            if not line.strip():
                continue
            try:
                data = json.loads(line)
                if data.get("type") == "match":
                    match_data = data.get("data", {})
                    path = match_data.get("path", {}).get("text", "")
                    line_number = match_data.get("line_number", 0)
                    lines = match_data.get("lines", {})
                    text = lines.get("text", "").strip() if isinstance(lines, dict) else ""

                    # Get relative path from docs root
                    try:
                        rel_path = str(Path(path).relative_to(self.docs_root))
                    except ValueError:
                        rel_path = path

                    # Get document title from path
                    doc_name = Path(rel_path).stem.replace("_", " ").replace("-", " ").title()

                    matches.append({
                        "file": rel_path,
                        "line": line_number,
                        "content": text[:300],  # Longer snippets for docs
                        "doc_name": doc_name,
                    })
            except json.JSONDecodeError:
                continue

        return matches[:self.max_results]

    async def execute(self, query: str, context: Dict[str, Any]) -> AgentResult:
        """
        Execute documentation search for the given query.

        Args:
            query: The user's query about documentation
            context: Additional context (uncertainty_type, keywords, etc.)

        Returns:
            AgentResult with search results and doc:line sources
        """
        start_time = time.perf_counter()
        agent_id = generate_agent_id()

        # Check ripgrep availability
        if not self.available():
            elapsed_ms = (time.perf_counter() - start_time) * 1000
            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result=None,
                sources=[],
                confidence=0.0,
                elapsed_ms=elapsed_ms,
                error="ripgrep (rg) not available",
            )

        # Check if docs exist
        if not self._has_docs():
            elapsed_ms = (time.perf_counter() - start_time) * 1000
            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result={
                    "status": "no_docs",
                    "message": "No documentation files found",
                    "docs_root": str(self.docs_root),
                    "suggested_action": "Run 'python tools/build_docs_index.py' to download docs",
                },
                sources=[],
                confidence=0.0,
                elapsed_ms=elapsed_ms,
                error=f"No documentation in {self.docs_root}. Run tools/build_docs_index.py to populate.",
            )

        # Extract search terms from query
        search_terms = self._extract_search_terms(query)

        if not search_terms:
            elapsed_ms = (time.perf_counter() - start_time) * 1000
            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result=None,
                sources=[],
                confidence=0.0,
                elapsed_ms=elapsed_ms,
                error="Could not extract search terms from query",
            )

        # Run search for each term (in thread pool to not block)
        all_matches = []
        loop = asyncio.get_event_loop()

        for term in search_terms:
            matches = await loop.run_in_executor(
                None,
                lambda t=term: self._run_ripgrep(t),
            )
            all_matches.extend(matches)

        # Deduplicate by file:line, prefer matches with more context
        seen = set()
        unique_matches = []
        for match in all_matches:
            key = f"{match['file']}:{match['line']}"
            if key not in seen:
                seen.add(key)
                unique_matches.append(match)

        unique_matches = unique_matches[:self.max_results]

        # Build sources list (docs/file.md:line format)
        sources = [f"docs/{m['file']}:{m['line']}" for m in unique_matches]

        # Compute confidence based on match quality
        if not unique_matches:
            confidence = 0.0
        elif len(unique_matches) >= 5:
            confidence = 0.9
        elif len(unique_matches) >= 3:
            confidence = 0.8
        elif len(unique_matches) >= 1:
            confidence = 0.6
        else:
            confidence = 0.3

        elapsed_ms = (time.perf_counter() - start_time) * 1000

        # Build result summary
        result_data = {
            "search_terms": search_terms,
            "docs_root": str(self.docs_root),
            "match_count": len(unique_matches),
            "matches": unique_matches,
        }

        return AgentResult(
            agent_id=agent_id,
            agent_type=self.agent_type,
            query=query,
            result=result_data,
            sources=sources,
            confidence=confidence,
            elapsed_ms=elapsed_ms,
            error=None if unique_matches else "No matches found in documentation",
        )


# Auto-register the agent when this module is imported
from . import register_agent as _register

try:
    _register(SearchAgent())
except ValueError:
    pass  # Already registered
