"""RAG search tool - Search knowledge base for accurate documentation."""

from __future__ import annotations

import asyncio
import concurrent.futures
import threading
from typing import TYPE_CHECKING, Any, Callable

from agenai.agent.tools.base import Tool, ToolResult

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession


class _BackgroundLoop:
    """Manages a dedicated background thread with its own event loop.

    This solves the async/sync boundary issue by running async database
    operations in a dedicated thread that owns its own event loop and
    database connections.
    """

    _instance: "_BackgroundLoop | None" = None
    _lock = threading.Lock()

    def __new__(cls) -> "_BackgroundLoop":
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance._initialized = False
            return cls._instance

    def __init__(self) -> None:
        if self._initialized:
            return
        self._initialized = True
        self._loop: asyncio.AbstractEventLoop | None = None
        self._thread: threading.Thread | None = None
        self._start_event = threading.Event()
        # Engine and session maker created lazily in the background loop
        self._engine: Any = None
        self._session_maker: Any = None

    def _run_loop(self) -> None:
        """Run the event loop in the background thread."""
        self._loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self._loop)
        self._start_event.set()
        self._loop.run_forever()

    def ensure_started(self) -> None:
        """Ensure the background thread and loop are running."""
        if self._thread is None or not self._thread.is_alive():
            self._start_event.clear()
            self._thread = threading.Thread(target=self._run_loop, daemon=True)
            self._thread.start()
            self._start_event.wait()

    def run_coroutine(self, coro: Any) -> Any:
        """Run a coroutine in the background loop and wait for result."""
        self.ensure_started()
        if self._loop is None:
            raise RuntimeError("Background loop not started")
        future = asyncio.run_coroutine_threadsafe(coro, self._loop)
        return future.result(timeout=30.0)

    def get_session_maker(self) -> Any:
        """Get or create a session maker for the background loop.

        Creates the engine and session maker lazily on first access,
        ensuring connections are bound to the background loop.
        """
        if self._session_maker is None:
            # Import here to avoid circular dependencies
            from sqlalchemy.ext.asyncio import (
                AsyncSession,
                async_sessionmaker,
                create_async_engine,
            )

            # Get database URL from settings
            from app.core.config import get_settings

            settings = get_settings()
            self._engine = create_async_engine(settings.database_url, echo=False)
            self._session_maker = async_sessionmaker(
                self._engine, class_=AsyncSession, expire_on_commit=False
            )
        return self._session_maker


class RAGSearchTool(Tool):
    """Search W3Schools documentation for accurate programming information.

    This tool uses vector similarity search to find relevant documentation
    chunks that can help provide accurate, reference-backed responses.
    """

    def __init__(
        self,
        retriever: Any,
        session_maker: Any = None,  # Not used - kept for API compatibility
        default_language: str = "python",
    ):
        """Initialize the RAG search tool.

        Args:
            retriever: RAGRetriever instance for searching.
            session_maker: Deprecated - session is created in background loop.
            default_language: Default programming language to search in.
        """
        self._retriever = retriever
        self._default_language = default_language
        self._bg_loop = _BackgroundLoop()

    @property
    def name(self) -> str:
        return "search_docs"

    @property
    def description(self) -> str:
        return """Search W3Schools documentation for accurate programming information.

Use this tool to:
- Find correct syntax and usage examples before explaining concepts
- Verify your understanding of language features
- Get reference documentation for methods and functions
- Find code examples to demonstrate concepts

Always use this tool when answering questions about:
- Specific syntax or method usage
- How built-in functions work
- Best practices for common patterns

The results include source URLs for attribution."""

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query (e.g., 'list append method', 'for loop syntax', 'dictionary comprehension')",
                },
                "language": {
                    "type": "string",
                    "description": "Programming language to search in",
                    "enum": ["python", "javascript"],
                    "default": "python",
                },
                "num_results": {
                    "type": "integer",
                    "description": "Number of results to return (1-5)",
                    "default": 3,
                    "minimum": 1,
                    "maximum": 5,
                },
            },
            "required": ["query"],
        }

    def execute(
        self,
        query: str,
        language: str | None = None,
        num_results: int = 3,
    ) -> ToolResult:
        """Execute the search query.

        Args:
            query: Search query string.
            language: Programming language to search in.
            num_results: Number of results to return.

        Returns:
            ToolResult with formatted search results.
        """
        lang = language or self._default_language
        num_results = max(1, min(5, num_results))

        try:
            # Run async search in dedicated background thread with its own event loop
            # This avoids asyncpg cross-loop issues
            results = self._bg_loop.run_coroutine(
                self._async_search(query, lang, num_results)
            )
        except concurrent.futures.TimeoutError:
            return ToolResult.fail("Search timed out. Please try again.")
        except Exception as e:
            return ToolResult.fail(f"Search failed: {e}")

        if not results:
            return ToolResult.ok(
                f"No documentation found for '{query}' in {lang}. "
                "Try a different search query or explain based on your knowledge."
            )

        # Format results
        output_parts = [f"**W3Schools Documentation Results ({lang.title()}):**\n"]

        for i, result in enumerate(results, 1):
            output_parts.append(f"### {i}. {result.title}")
            output_parts.append(f"*Source: {result.url}* (relevance: {result.score:.0%})\n")
            # Limit text length for conciseness
            text = result.text
            if len(text) > 600:
                text = text[:600] + "..."
            output_parts.append(text)
            output_parts.append("")  # Empty line between results

        return ToolResult.ok("\n".join(output_parts))

    async def _async_search(
        self,
        query: str,
        language: str,
        top_k: int,
    ) -> list:
        """Perform async search using background loop's session maker."""
        session_maker = self._bg_loop.get_session_maker()
        async with session_maker() as session:
            return await self._retriever.search(
                session,
                query,
                language=language,
                top_k=top_k,
            )
