"""
Codebase Agent - Search helix-cdc codebase using ripgrep

WO-ECHO-UNCERTAINTY-DISPATCH: Agent for searching the local codebase
to answer questions about code structure, implementations, and files.

Usage:
    from helix_cdc.echo.agents.codebase_agent import CodebaseAgent
    from helix_cdc.echo.agents import register_agent

    register_agent(CodebaseAgent())
"""

import asyncio
import json
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,
)


class CodebaseAgent(DispatchableAgent):
    """
    Agent for searching the helix-cdc codebase.

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

    Safety: Hardcoded to only search within PROJECT_ROOT (helix-cdc).
    """

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

    def __init__(self, max_results: int = 10):
        """
        Initialize the codebase agent.

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

    def available(self) -> bool:
        """Check if ripgrep is available."""
        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 _extract_search_terms(self, query: str) -> List[str]:
        """
        Extract search terms from a natural language query.

        Examples:
            "Where is the spatial memory implemented?" -> ["spatial", "memory"]
            "What file contains detect_geometry_intent?" -> ["detect_geometry_intent"]
            "How does the uncertainty detector work?" -> ["uncertainty", "detector"]
        """
        # Remove common question words
        stop_words = {
            "what", "where", "how", "which", "is", "the", "a", "an", "in", "of",
            "does", "do", "can", "file", "files", "function", "class", "module",
            "implemented", "defined", "contains", "has", "this", "that", "are",
            "code", "codebase", "implementation", "work", "works", "find",
        }

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

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

        if identifiers:
            return identifiers[:3]  # Limit to top 3 identifiers

        # Fall back to significant words
        words = re.findall(r'\b\w+\b', query.lower())
        significant = [w for w in words if w not in stop_words and len(w) > 2]

        return significant[:3] if significant else ["echo"]  # Default fallback

    def _run_ripgrep(
        self,
        pattern: str,
        file_type: Optional[str] = "py",
        context_lines: int = 1,
    ) -> List[Dict[str, Any]]:
        """
        Run ripgrep with the given pattern.

        Returns list of match dicts with file, line, content, context.
        """
        args = [
            "rg",
            "--json",
            "-i",  # Case insensitive
            "--max-count", str(self.max_results * 2),  # Get extras for filtering
        ]

        if file_type:
            args.extend(["--type", file_type])

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

        args.append(pattern)
        args.append(str(PROJECT_ROOT))

        try:
            result = subprocess.run(
                args,
                capture_output=True,
                text=True,
                timeout=30,
                cwd=str(PROJECT_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
                    try:
                        rel_path = str(Path(path).relative_to(PROJECT_ROOT))
                    except ValueError:
                        rel_path = path

                    matches.append({
                        "file": rel_path,
                        "line": line_number,
                        "content": text[:200],  # Truncate long lines
                    })
            except json.JSONDecodeError:
                continue

        return matches[:self.max_results]

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

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

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

        if not self.available():
            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result=None,
                sources=[],
                confidence=0.0,
                elapsed_ms=0.0,
                error="ripgrep (rg) not available",
            )

        # 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
        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
        sources = [f"{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) >= 2:
            confidence = 0.7
        else:
            confidence = 0.5

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

        # Build result summary
        result_data = {
            "search_terms": search_terms,
            "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",
        )


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

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