"""
UnSearch Python Client - Comprehensive API client.

This client provides full access to all UnSearch backend capabilities:
- Agent API (Tavily-compatible)
- Core Search API
- RAG API
- Neural API (Exa-compatible)
- Topic Monitoring API
- Verification API (Fact-checking)
"""
import os
import httpx
from typing import List, Dict, Any, Optional, Literal, Union
from dataclasses import dataclass, field

from unsearch.exceptions import UnSearchError, UnSearchAPIError


# =============================================================================
# Data Classes - Agent API
# =============================================================================

@dataclass
class SearchResult:
    """Single search result."""
    title: str
    url: str
    content: str
    score: float
    raw_content: Optional[str] = None


@dataclass  
class SearchResponse:
    """Search response containing results and metadata."""
    query: str
    results: List[SearchResult]
    answer: Optional[str] = None
    images: List[Dict[str, Any]] = field(default_factory=list)
    response_time: float = 0.0
    model_used: Optional[str] = None
    query_complexity: Optional[str] = None


@dataclass
class ExtractedContent:
    """Extracted content from a URL."""
    url: str
    raw_content: str
    images: Optional[List[str]] = None
    failed: bool = False
    error: Optional[str] = None


@dataclass
class ExtractResponse:
    """Extract response."""
    results: List[ExtractedContent]
    failed_urls: List[str] = field(default_factory=list)
    response_time: float = 0.0


@dataclass
class ResearchResponse:
    """Deep research response."""
    query: str
    executive_summary: Optional[str] = None
    detailed_analysis: Optional[str] = None
    key_findings: List[str] = field(default_factory=list)
    sources: List[SearchResult] = field(default_factory=list)
    methodology: Dict[str, Any] = field(default_factory=dict)
    model_used: str = ""
    response_time: float = 0.0


# =============================================================================
# Data Classes - RAG API
# =============================================================================

@dataclass
class RAGSource:
    """RAG research source."""
    url: str
    title: str
    content: str
    summary: Optional[str] = None
    relevance_score: float = 0.0
    word_count: int = 0
    engine: Optional[str] = None
    scraped_at: Optional[str] = None


@dataclass
class RAGResearchResponse:
    """RAG research response."""
    topic: str
    corpus_id: str
    sources: List[RAGSource]
    total_sources_found: int
    queries_executed: int
    processing_time_ms: int
    depth: str


@dataclass
class RAGSearchResponse:
    """RAG search response."""
    query: str
    context: Optional[str] = None
    sources: List[RAGSource] = field(default_factory=list)
    source_count: int = 0
    processing_time_ms: int = 0


@dataclass
class SemanticSearchResult:
    """Semantic search result."""
    id: str
    score: float
    url: str
    title: str
    summary: str
    relevance_score: Optional[float] = None


# =============================================================================
# Data Classes - Neural API
# =============================================================================

@dataclass
class NeuralSearchResult:
    """Neural search result."""
    title: str
    url: str
    content: str
    score: float
    published_date: Optional[str] = None
    author: Optional[str] = None
    highlights: Optional[List[str]] = None


@dataclass
class NeuralSearchResponse:
    """Neural search response."""
    query: str
    results: List[NeuralSearchResult]
    expanded_queries: Optional[List[str]] = None
    autoprompt_used: bool = False
    search_type: str = "neural"
    response_time_ms: int = 0


@dataclass
class SimilarContentResponse:
    """Similar content response."""
    source: str
    similar: List[NeuralSearchResult]
    response_time_ms: int = 0


@dataclass
class Highlight:
    """Content highlight."""
    text: str
    relevance: float
    start_index: Optional[int] = None


@dataclass
class SearchPrediction:
    """Search prediction."""
    query: str
    confidence: float
    reason: str


# =============================================================================
# Data Classes - Monitoring API
# =============================================================================

@dataclass
class TopicMonitor:
    """Topic monitor."""
    id: str
    topic: str
    keywords: List[str]
    sources: Optional[List[str]] = None
    check_interval_minutes: int = 60
    webhook_url: Optional[str] = None
    deep_analysis: bool = False
    status: str = "active"
    last_checked: Optional[str] = None
    alerts_sent: int = 0
    created_at: Optional[str] = None


@dataclass
class MonitorResult:
    """Monitor check result."""
    id: str
    monitor_id: str
    timestamp: str
    new_results: List[Dict[str, Any]]
    analysis: Optional[str] = None


# =============================================================================
# Data Classes - Verification API
# =============================================================================

@dataclass
class SourceEvidence:
    """Source evidence for claim verification."""
    title: str
    url: str
    snippet: str
    stance: str  # supporting, contradicting, neutral
    credibility_score: Optional[float] = None


@dataclass
class ClaimVerificationResponse:
    """Claim verification response."""
    claim: str
    verdict: str  # true, false, partially_true, unverifiable, misleading
    confidence: float
    summary: str
    supporting_evidence: List[SourceEvidence]
    contradicting_evidence: List[SourceEvidence]
    key_facts: List[str]
    nuances: Optional[str] = None
    sources_checked: int = 0
    verification_time_ms: int = 0


@dataclass
class SourceCredibilityResponse:
    """Source credibility response."""
    domain: str
    credibility_score: float
    category: str
    bias_rating: str
    factual_reporting: str
    notes: Optional[str] = None
    last_updated: Optional[str] = None


# =============================================================================
# Data Classes - Enhanced API
# =============================================================================

@dataclass
class EnhancedScrapeResult:
    """Enhanced scrape result."""
    url: str
    title: Optional[str] = None
    text: Optional[str] = None
    extraction_success: bool = False
    word_count: int = 0
    content_quality_score: float = 0.0
    extracted_content: Any = None
    markdown: Optional[str] = None
    link_analysis: Optional[Dict[str, Any]] = None


@dataclass
class ChunkResult:
    """Content chunk result."""
    chunks: List[str] = field(default_factory=list)
    total_chunks: int = 0
    original_length: int = 0
    avg_chunk_length: float = 0.0
    strategy_used: str = ""


# =============================================================================
# Data Classes - Knowledge Graph API
# =============================================================================

@dataclass
class Entity:
    """Knowledge graph entity."""
    id: str
    type: str
    name: str
    description: Optional[str] = None
    source_count: int = 0
    metadata: Optional[Dict[str, Any]] = None


@dataclass
class Relationship:
    """Knowledge graph relationship."""
    from_entity: str
    to_entity: str
    type: str
    weight: float = 1.0
    source: Optional[str] = None


@dataclass
class Person:
    """Person entity from knowledge graph."""
    id: str
    name: str
    title: Optional[str] = None
    organization: Optional[str] = None
    email: Optional[str] = None
    expertise: List[str] = field(default_factory=list)
    recent_activity: Optional[List[Dict[str, Any]]] = None
    relevance_score: float = 0.0


# =============================================================================
# Data Classes - Agent Registration API
# =============================================================================

@dataclass
class AgentAccessInfo:
    """Agent access information."""
    level: str = "sandbox"
    is_claimed: bool = False
    daily_limit: int = 25
    daily_used: int = 0
    daily_remaining: int = 25
    days_remaining: Optional[int] = None
    expires_at: Optional[str] = None


@dataclass
class AgentRegisterResponse:
    """Agent registration response."""
    api_key: str
    claim_url: str
    claim_code: str
    agent_name: str
    access: AgentAccessInfo = field(default_factory=AgentAccessInfo)
    message: str = ""


@dataclass
class AgentStatusResponse:
    """Agent status response."""
    agent_name: str
    description: Optional[str] = None
    access: AgentAccessInfo = field(default_factory=AgentAccessInfo)
    claim_url: Optional[str] = None
    action_required: Optional[str] = None
    human_owner: Optional[Dict[str, Any]] = None


class UnSearchClient:
    """
    UnSearch API client - comprehensive access to all backend capabilities.
    
    Drop-in replacement for TavilyClient with extended features:
    - Agent API (Tavily-compatible)
    - Core Search API  
    - RAG API
    - Neural API (Exa-compatible)
    - Topic Monitoring API
    - Verification API (Fact-checking)
    
    Args:
        api_key: Your UnSearch API key (or set UNSEARCH_API_KEY env var)
        base_url: API base URL (default: https://api.unsearch.dev)
        timeout: Request timeout in seconds
        zero_retention: Enable zero-retention mode (no data stored)
        
    Example:
        >>> client = UnSearchClient(api_key="your-key")
        >>> response = client.search("What is Python?")
        >>> print(response.answer)
    """
    
    DEFAULT_BASE_URL = "https://api.unsearch.dev"
    
    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: Optional[str] = None,
        timeout: float = 30.0,
        zero_retention: bool = False
    ):
        self.api_key = api_key or os.environ.get("UNSEARCH_API_KEY")
        if not self.api_key:
            raise UnSearchError(
                "API key required. Pass api_key or set UNSEARCH_API_KEY environment variable."
            )
        
        self.base_url = (base_url or os.environ.get("UNSEARCH_BASE_URL") or self.DEFAULT_BASE_URL).rstrip("/")
        self.timeout = timeout
        self.zero_retention = zero_retention
        
        self._client = httpx.Client(
            base_url=self.base_url,
            timeout=timeout,
            headers=self._get_headers()
        )
    
    def _get_headers(self) -> Dict[str, str]:
        """Get request headers."""
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json",
            "User-Agent": "unsearch-python/0.1.0"
        }
        if self.zero_retention:
            headers["X-Zero-Retention"] = "true"
        return headers

    # =========================================================================
    # Agent API (Tavily-compatible)
    # =========================================================================
    
    def search(
        self,
        query: str,
        search_depth: Literal["basic", "advanced", "fast", "ultra-fast"] = "basic",
        max_results: int = 5,
        topic: Literal["general", "news", "finance"] = "general",
        include_answer: Union[bool, Literal["basic", "advanced", "production"]] = False,
        include_raw_content: Union[bool, Literal["markdown", "text"]] = False,
        include_images: bool = False,
        include_image_descriptions: bool = False,
        include_domains: Optional[List[str]] = None,
        exclude_domains: Optional[List[str]] = None,
        model: Optional[Literal["auto", "speed", "quality", "reasoning", "production"]] = None,
        rerank: bool = False,
        check_safety: bool = False,
    ) -> SearchResponse:
        """
        Execute a search query (Tavily-compatible).
        
        Args:
            query: The search query
            search_depth: Controls latency vs relevance
            max_results: Maximum number of results (1-20)
            topic: Search category ("general", "news", "finance")
            include_answer: Include LLM-generated answer
            include_raw_content: Include full page content
            include_images: Include image results
            include_image_descriptions: Add descriptions to images
            include_domains: Only include these domains
            exclude_domains: Exclude these domains
            model: Model selection (auto, speed, quality, reasoning, production)
            rerank: Rerank results using AI
            check_safety: Run content safety checks
            
        Returns:
            SearchResponse with results, optional answer, and metadata
        """
        payload = {
            "query": query,
            "search_depth": search_depth,
            "max_results": max_results,
            "topic": topic,
            "include_answer": include_answer,
            "include_raw_content": include_raw_content,
            "include_images": include_images,
            "include_image_descriptions": include_image_descriptions,
            "rerank": rerank,
            "check_safety": check_safety,
        }
        
        if include_domains:
            payload["include_domains"] = include_domains
        if exclude_domains:
            payload["exclude_domains"] = exclude_domains
        if model:
            payload["model"] = model
        
        response = self._post("/api/v1/agent/search", payload)
        
        results = [
            SearchResult(
                title=r["title"],
                url=r["url"],
                content=r["content"],
                score=r.get("score", 0.0),
                raw_content=r.get("raw_content")
            )
            for r in response.get("results", [])
        ]
        
        return SearchResponse(
            query=response["query"],
            results=results,
            answer=response.get("answer"),
            images=response.get("images", []),
            response_time=response.get("response_time", 0.0),
            model_used=response.get("model_used"),
            query_complexity=response.get("query_complexity")
        )
    
    def extract(
        self,
        urls: List[str],
        include_images: bool = False,
        extract_depth: Literal["basic", "advanced"] = "basic"
    ) -> ExtractResponse:
        """
        Extract content from URLs (Tavily-compatible).
        
        Args:
            urls: List of URLs to extract content from
            include_images: Include images in extraction
            extract_depth: "basic" or "advanced" (with JS rendering)
            
        Returns:
            ExtractResponse with results and failed_urls
        """
        payload = {
            "urls": urls,
            "include_images": include_images,
            "extract_depth": extract_depth
        }
        
        response = self._post("/api/v1/agent/extract", payload)
        
        results = [
            ExtractedContent(
                url=r["url"],
                raw_content=r.get("raw_content", ""),
                images=r.get("images"),
                failed=r.get("failed", False),
                error=r.get("error")
            )
            for r in response.get("results", [])
        ]
        
        return ExtractResponse(
            results=results,
            failed_urls=response.get("failed_urls", []),
            response_time=response.get("response_time", 0.0)
        )

    def research(
        self,
        query: str,
        depth: Literal["quick", "standard", "deep", "comprehensive"] = "standard",
        max_sources: int = 10,
        include_analysis: bool = True,
        include_summary: bool = True,
        focus_areas: Optional[List[str]] = None,
    ) -> ResearchResponse:
        """
        Deep research with AI reasoning (UnSearch exclusive).
        
        Args:
            query: Research question or topic
            depth: Research depth (quick, standard, deep, comprehensive)
            max_sources: Maximum sources to analyze
            include_analysis: Include detailed analysis
            include_summary: Include executive summary
            focus_areas: Specific areas to focus on
            
        Returns:
            ResearchResponse with findings and sources
        """
        payload = {
            "query": query,
            "depth": depth,
            "max_sources": max_sources,
            "include_analysis": include_analysis,
            "include_summary": include_summary,
        }
        if focus_areas:
            payload["focus_areas"] = focus_areas
        
        response = self._post("/api/v1/agent/research", payload)
        
        sources = [
            SearchResult(
                title=s["title"],
                url=s["url"],
                content=s.get("content", ""),
                score=s.get("score", 0.0),
                raw_content=s.get("raw_content")
            )
            for s in response.get("sources", [])
        ]
        
        return ResearchResponse(
            query=response["query"],
            executive_summary=response.get("executive_summary"),
            detailed_analysis=response.get("detailed_analysis"),
            key_findings=response.get("key_findings", []),
            sources=sources,
            methodology=response.get("methodology", {}),
            model_used=response.get("model_used", ""),
            response_time=response.get("response_time", 0.0)
        )

    def list_models(self) -> Dict[str, Any]:
        """List available AI models."""
        return self._get("/api/v1/agent/models")

    def agent_health(self) -> Dict[str, Any]:
        """Agent API health check."""
        return self._get("/api/v1/agent/health")
    
    def qna_search(
        self,
        query: str,
        search_depth: Literal["basic", "advanced"] = "advanced",
        max_results: int = 5,
        topic: Literal["general", "news", "finance"] = "general",
    ) -> str:
        """Q&A search - returns just the answer string (Tavily-compatible)."""
        response = self.search(
            query=query,
            search_depth=search_depth,
            max_results=max_results,
            topic=topic,
            include_answer=True
        )
        return response.answer or ""
    
    def get_search_context(
        self,
        query: str,
        search_depth: Literal["basic", "advanced"] = "basic",
        max_results: int = 5,
        max_tokens: int = 4000,
    ) -> str:
        """Get search context for RAG (Tavily-compatible)."""
        response = self.search(
            query=query,
            search_depth=search_depth,
            max_results=max_results,
            include_raw_content=True
        )
        
        context_parts = []
        total_chars = 0
        max_chars = max_tokens * 4
        
        for result in response.results:
            content = result.raw_content or result.content
            if total_chars + len(content) > max_chars:
                break
            context_parts.append(f"Source: {result.title}\nURL: {result.url}\n{content}")
            total_chars += len(content)
        
        return "\n\n---\n\n".join(context_parts)

    # =========================================================================
    # Core Search API
    # =========================================================================

    def core_search(
        self,
        query: str,
        engines: Optional[List[str]] = None,
        max_results: int = 10,
        language: str = "en",
        safe_search: Literal["off", "moderate", "strict"] = "moderate",
        scrape_content: bool = False,
        scrape_selectors: Optional[Dict[str, str]] = None,
        include_images: bool = False,
        include_links: bool = False,
        js_mode: bool = False,
        output_format: Literal["json", "markdown", "text"] = "json",
        cache_ttl: int = 3600,
    ) -> Dict[str, Any]:
        """
        Core search with full options.
        
        Args:
            query: Search query
            engines: List of engines to use
            max_results: Maximum results
            language: Language code
            safe_search: Safe search level
            scrape_content: Scrape page content
            scrape_selectors: CSS selectors for scraping
            include_images: Include images
            include_links: Include links
            js_mode: Enable JavaScript rendering
            output_format: Output format
            cache_ttl: Cache TTL in seconds
            
        Returns:
            Full search response with metadata
        """
        payload = {
            "query": query,
            "max_results": max_results,
            "language": language,
            "safe_search": safe_search,
            "scrape_content": scrape_content,
            "include_images": include_images,
            "include_links": include_links,
            "js_mode": js_mode,
            "output_format": output_format,
            "cache_ttl": cache_ttl,
        }
        if engines:
            payload["engines"] = engines
        if scrape_selectors:
            payload["scrape_selectors"] = scrape_selectors
            
        return self._post("/api/v1/search/", payload)

    def batch_search(
        self,
        queries: List[str],
        engines: Optional[List[str]] = None,
        max_results_per_query: int = 10,
        scrape_content: bool = False,
        parallel_requests: int = 3,
    ) -> Dict[str, Any]:
        """Batch search - multiple queries in parallel."""
        payload = {
            "queries": queries,
            "max_results_per_query": max_results_per_query,
            "scrape_content": scrape_content,
            "parallel_requests": parallel_requests,
        }
        if engines:
            payload["engines"] = engines
            
        return self._post("/api/v1/search/batch", payload)

    def list_engines(self) -> Dict[str, Any]:
        """List available search engines."""
        return self._get("/api/v1/search/engines")

    def search_health(self) -> Dict[str, Any]:
        """Search API health check."""
        return self._get("/api/v1/search/health")

    # =========================================================================
    # RAG API
    # =========================================================================

    def rag_research(
        self,
        topic: str,
        depth: Literal["quick", "standard", "deep"] = "standard",
        num_queries: Optional[int] = None,
        target_sources: Optional[int] = None,
        scrape_content: bool = True,
        generate_embeddings: bool = True,
        engines: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Deep RAG research on a topic."""
        payload = {
            "topic": topic,
            "depth": depth,
            "scrape_content": scrape_content,
            "generate_embeddings": generate_embeddings,
        }
        if num_queries:
            payload["num_queries"] = num_queries
        if target_sources:
            payload["target_sources"] = target_sources
        if engines:
            payload["engines"] = engines
        return self._post("/api/v1/rag/research", payload)

    def rag_search(
        self,
        query: str,
        max_sources: int = 5,
        scrape_content: bool = True,
        include_context: bool = True,
        engines: Optional[List[str]] = None,
    ) -> RAGSearchResponse:
        """Quick RAG-optimized search."""
        payload = {
            "query": query,
            "max_sources": max_sources,
            "scrape_content": scrape_content,
            "include_context": include_context,
        }
        if engines:
            payload["engines"] = engines
        response = self._post("/api/v1/rag/search", payload)
        
        sources = [
            RAGSource(
                url=s["url"],
                title=s["title"],
                content=s.get("content", ""),
                summary=s.get("summary"),
                relevance_score=s.get("relevance_score", 0.0),
                word_count=s.get("word_count", 0),
                engine=s.get("metadata", {}).get("engine"),
                scraped_at=s.get("scraped_at")
            )
            for s in response.get("sources", [])
        ]
        
        return RAGSearchResponse(
            query=response["query"],
            context=response.get("context"),
            sources=sources,
            source_count=response.get("source_count", len(sources)),
            processing_time_ms=response.get("processing_time_ms", 0)
        )

    def semantic_search(
        self,
        corpus_id: str,
        query: str,
        limit: int = 10,
        min_relevance: float = 0.5,
    ) -> Dict[str, Any]:
        """Semantic search over a research corpus."""
        payload = {
            "corpus_id": corpus_id,
            "query": query,
            "limit": limit,
            "min_relevance": min_relevance,
        }
        return self._post("/api/v1/rag/semantic-search", payload)

    def image_search(
        self,
        query: str,
        max_results: int = 10,
        engines: Optional[List[str]] = None,
        safe_search: Literal["off", "moderate", "strict"] = "moderate",
    ) -> Dict[str, Any]:
        """Image search."""
        payload = {
            "query": query,
            "max_results": max_results,
            "safe_search": safe_search,
        }
        if engines:
            payload["engines"] = engines
        return self._post("/api/v1/rag/images", payload)

    def generate_queries(self, topic: str, num_queries: int = 10) -> Dict[str, Any]:
        """Generate research queries for a topic."""
        return self._post(f"/api/v1/rag/generate-queries?topic={topic}&num_queries={num_queries}", {})

    def list_corpora(self) -> Dict[str, Any]:
        """List all research corpora."""
        return self._get("/api/v1/rag/corpus")

    def get_corpus_info(self, corpus_id: str) -> Dict[str, Any]:
        """Get corpus info."""
        return self._get(f"/api/v1/rag/corpus/{corpus_id}/info")

    def delete_corpus(self, corpus_id: str) -> Dict[str, Any]:
        """Delete a research corpus."""
        return self._delete(f"/api/v1/rag/corpus/{corpus_id}")

    # =========================================================================
    # Neural API (Exa-compatible)
    # =========================================================================

    def neural_search(
        self,
        query: str,
        num_results: int = 10,
        use_autoprompt: bool = False,
        include_highlights: bool = True,
        include_domains: Optional[List[str]] = None,
        exclude_domains: Optional[List[str]] = None,
        category: Optional[Literal["general", "news", "academic", "tech", "finance"]] = None,
    ) -> NeuralSearchResponse:
        """Neural/semantic search with optional auto-prompting."""
        payload = {
            "query": query,
            "num_results": num_results,
            "use_autoprompt": use_autoprompt,
            "include_highlights": include_highlights,
        }
        if include_domains:
            payload["include_domains"] = include_domains
        if exclude_domains:
            payload["exclude_domains"] = exclude_domains
        if category:
            payload["category"] = category
            
        response = self._post("/api/v1/neural/search", payload)
        
        results = [
            NeuralSearchResult(
                title=r["title"],
                url=r["url"],
                content=r["content"],
                score=r.get("score", 0.0),
                published_date=r.get("published_date"),
                author=r.get("author"),
                highlights=r.get("highlights")
            )
            for r in response.get("results", [])
        ]
        
        return NeuralSearchResponse(
            query=response["query"],
            results=results,
            expanded_queries=response.get("expanded_queries"),
            autoprompt_used=response.get("autoprompt_used", False),
            search_type=response.get("search_type", "neural"),
            response_time_ms=response.get("response_time_ms", 0)
        )

    def find_similar(
        self,
        url: Optional[str] = None,
        text: Optional[str] = None,
        num_results: int = 10,
        exclude_source: bool = True,
    ) -> Dict[str, Any]:
        """Find content similar to a URL or text."""
        payload = {
            "num_results": num_results,
            "exclude_source": exclude_source,
        }
        if url:
            payload["url"] = url
        if text:
            payload["text"] = text
        return self._post("/api/v1/neural/similar", payload)

    def extract_highlights(
        self,
        query: str,
        content: str,
        num_highlights: int = 3,
    ) -> Dict[str, Any]:
        """Extract key highlights from content."""
        payload = {
            "query": query,
            "content": content,
            "num_highlights": num_highlights,
        }
        return self._post("/api/v1/neural/highlights", payload)

    def predictive_search(
        self,
        context: Optional[str] = None,
        recent_searches: Optional[List[str]] = None,
        num_predictions: int = 5,
    ) -> Dict[str, Any]:
        """Predictive search - predict what user might search next."""
        payload = {"num_predictions": num_predictions}
        if context:
            payload["context"] = context
        if recent_searches:
            payload["recent_searches"] = recent_searches
        return self._post("/api/v1/neural/predictive", payload)

    def list_neural_categories(self) -> Dict[str, Any]:
        """List neural search categories."""
        return self._get("/api/v1/neural/categories")

    # =========================================================================
    # Topic Monitoring API
    # =========================================================================

    def create_monitor(
        self,
        topic: str,
        keywords: Optional[List[str]] = None,
        sources: Optional[List[str]] = None,
        check_interval_minutes: int = 60,
        webhook_url: Optional[str] = None,
        deep_analysis: bool = False,
    ) -> Dict[str, Any]:
        """Create a new topic monitor."""
        payload = {
            "topic": topic,
            "keywords": keywords or [],
            "check_interval_minutes": check_interval_minutes,
            "deep_analysis": deep_analysis,
        }
        if sources:
            payload["sources"] = sources
        if webhook_url:
            payload["webhook_url"] = webhook_url
        return self._post("/api/v1/monitor/topics", payload)

    def list_monitors(self) -> List[Dict[str, Any]]:
        """List all topic monitors."""
        return self._get("/api/v1/monitor/topics")

    def get_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Get a specific monitor."""
        return self._get(f"/api/v1/monitor/topics/{monitor_id}")

    def pause_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Pause a monitor."""
        return self._post(f"/api/v1/monitor/topics/{monitor_id}/pause", {})

    def resume_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Resume a paused monitor."""
        return self._post(f"/api/v1/monitor/topics/{monitor_id}/resume", {})

    def delete_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Delete a monitor."""
        return self._delete(f"/api/v1/monitor/topics/{monitor_id}")

    def trigger_monitor_check(self, monitor_id: str) -> Dict[str, Any]:
        """Manually trigger a monitor check."""
        return self._post(f"/api/v1/monitor/topics/{monitor_id}/check", {})

    def get_monitor_results(self, monitor_id: str, limit: int = 50) -> List[Dict[str, Any]]:
        """Get monitor results."""
        return self._get(f"/api/v1/monitor/topics/{monitor_id}/results?limit={limit}")

    # =========================================================================
    # Verification API (Fact-checking)
    # =========================================================================

    def verify_claim(
        self,
        claim: str,
        depth: Literal["quick", "thorough"] = "quick",
        include_sources: bool = True,
    ) -> ClaimVerificationResponse:
        """Verify a claim using multi-source fact-checking."""
        payload = {
            "claim": claim,
            "depth": depth,
            "include_sources": include_sources,
        }
        response = self._post("/api/v1/verify/claim", payload)
        
        supporting = [
            SourceEvidence(
                title=e["title"],
                url=e["url"],
                snippet=e["snippet"],
                stance=e["stance"],
                credibility_score=e.get("credibility_score")
            )
            for e in response.get("supporting_evidence", [])
        ]
        
        contradicting = [
            SourceEvidence(
                title=e["title"],
                url=e["url"],
                snippet=e["snippet"],
                stance=e["stance"],
                credibility_score=e.get("credibility_score")
            )
            for e in response.get("contradicting_evidence", [])
        ]
        
        return ClaimVerificationResponse(
            claim=response["claim"],
            verdict=response["verdict"],
            confidence=response["confidence"],
            summary=response["summary"],
            supporting_evidence=supporting,
            contradicting_evidence=contradicting,
            key_facts=response.get("key_facts", []),
            nuances=response.get("nuances"),
            sources_checked=response.get("sources_checked", 0),
            verification_time_ms=response.get("verification_time_ms", 0)
        )

    def check_source_credibility(self, url: str) -> SourceCredibilityResponse:
        """Check source/domain credibility."""
        response = self._post("/api/v1/verify/source", {"url": url})
        return SourceCredibilityResponse(
            domain=response["domain"],
            credibility_score=response["credibility_score"],
            category=response["category"],
            bias_rating=response["bias_rating"],
            factual_reporting=response["factual_reporting"],
            notes=response.get("notes"),
            last_updated=response.get("last_updated")
        )

    def batch_verify(self, claims: List[str]) -> Dict[str, Any]:
        """Batch verify multiple claims."""
        return self._post("/api/v1/verify/batch", {"claims": claims})

    def get_batch_verification_status(self, job_id: str) -> Dict[str, Any]:
        """Get batch verification status."""
        return self._get(f"/api/v1/verify/batch/{job_id}")

    # =========================================================================
    # Enhanced API (Advanced scraping & extraction)
    # =========================================================================

    def enhanced_search(
        self,
        query: str,
        engines: Optional[List[str]] = None,
        max_results: int = 10,
        scrape_content: bool = True,
        extraction_strategy: Literal["none", "cosine", "json_css", "regex", "llm"] = "none",
        content_filter: Literal["none", "pruning", "bm25", "llm"] = "none",
        generate_markdown: bool = False,
        **kwargs,
    ) -> Dict[str, Any]:
        """Advanced search with enhanced extraction features."""
        payload = {
            "query": query,
            "max_results": max_results,
            "scrape_content": scrape_content,
            "extraction_strategy": extraction_strategy,
            "content_filter": content_filter,
            "generate_markdown": generate_markdown,
            **kwargs,
        }
        if engines:
            payload["engines"] = engines
        return self._post("/api/v1/enhanced/search", payload)

    def enhanced_scrape(
        self,
        urls: List[str],
        extraction_strategy: Literal["none", "cosine", "json_css", "regex", "llm"] = "none",
        content_filter: Literal["none", "pruning", "bm25", "llm"] = "none",
        generate_markdown: bool = False,
        **kwargs,
    ) -> Dict[str, Any]:
        """Direct URL scraping with advanced features."""
        payload = {
            "urls": urls,
            "extraction_strategy": extraction_strategy,
            "content_filter": content_filter,
            "generate_markdown": generate_markdown,
            **kwargs,
        }
        return self._post("/api/v1/enhanced/scrape", payload)

    def enhanced_features(self) -> Dict[str, Any]:
        """List available enhanced features."""
        return self._get("/api/v1/enhanced/features")

    def extract_tables(
        self,
        html_content: str,
        base_url: Optional[str] = None,
        strategy: Literal["default", "llm", "smart"] = "default",
    ) -> Dict[str, Any]:
        """Extract tables from HTML content."""
        payload = {
            "html_content": html_content,
            "strategy": strategy,
        }
        if base_url:
            payload["base_url"] = base_url
        return self._post("/api/v1/enhanced/extract-tables", payload)

    def chunk_content(
        self,
        text: str,
        strategy: Literal["paragraph", "sentence", "fixed", "topic", "hybrid"] = "paragraph",
        config: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Chunk text content for RAG pipelines."""
        payload = {
            "text": text,
            "strategy": strategy,
        }
        if config:
            payload["config"] = config
        return self._post("/api/v1/enhanced/chunk-content", payload)

    def discover_urls(
        self,
        base_url: str,
        source: Literal["sitemap", "cc", "crawl"] = "sitemap",
        max_urls: Optional[int] = None,
        pattern: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Discover URLs from sitemaps or crawling."""
        payload = {
            "base_url": base_url,
            "source": source,
        }
        if max_urls:
            payload["max_urls"] = max_urls
        if pattern:
            payload["pattern"] = pattern
        return self._post("/api/v1/enhanced/discover-urls", payload)

    def enhanced_performance(self) -> Dict[str, Any]:
        """Get enhanced API performance metrics."""
        return self._get("/api/v1/enhanced/performance")

    # =========================================================================
    # Knowledge Graph API
    # =========================================================================

    def knowledge_extract(
        self,
        text: str,
        types: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Extract entities and relationships from text."""
        payload = {"text": text}
        if types:
            payload["types"] = types
        return self._post("/api/v1/knowledge/extract", payload)

    def knowledge_search(
        self,
        query: str,
        entity_types: Optional[List[str]] = None,
        max_results: Optional[int] = None,
        include_relationships: bool = True,
    ) -> Dict[str, Any]:
        """Search the knowledge graph."""
        payload = {
            "query": query,
            "include_relationships": include_relationships,
        }
        if entity_types:
            payload["entity_types"] = entity_types
        if max_results:
            payload["max_results"] = max_results
        return self._post("/api/v1/knowledge/search", payload)

    def knowledge_people(
        self,
        query: str,
        organization: Optional[str] = None,
        role: Optional[str] = None,
        max_results: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Search for people in the knowledge graph."""
        payload = {"query": query}
        if organization:
            payload["organization"] = organization
        if role:
            payload["role"] = role
        if max_results:
            payload["max_results"] = max_results
        return self._post("/api/v1/knowledge/people", payload)

    def knowledge_get_entity(self, entity_id: str) -> Dict[str, Any]:
        """Get a specific entity from the knowledge graph."""
        return self._get(f"/api/v1/knowledge/entities/{entity_id}")

    def knowledge_graph(
        self,
        entity_types: Optional[List[str]] = None,
        limit: int = 100,
    ) -> Dict[str, Any]:
        """Get knowledge graph structure."""
        params = f"?limit={limit}"
        if entity_types:
            params += "&" + "&".join(f"entity_types={t}" for t in entity_types)
        return self._get(f"/api/v1/knowledge/graph{params}")

    def knowledge_delete_entity(self, entity_id: str) -> Dict[str, Any]:
        """Delete an entity from the knowledge graph."""
        return self._delete(f"/api/v1/knowledge/entities/{entity_id}")

    # =========================================================================
    # Agent Registration API
    # =========================================================================

    def register_agent(
        self,
        name: str,
        description: Optional[str] = None,
    ) -> AgentRegisterResponse:
        """Register a new AI agent (sandbox mode)."""
        payload = {"name": name}
        if description:
            payload["description"] = description
        response = self._post("/api/v1/agents/register", payload)
        access_data = response.get("access", {})
        access = AgentAccessInfo(
            level=access_data.get("level", "sandbox"),
            is_claimed=access_data.get("is_claimed", False),
            daily_limit=access_data.get("daily_limit", 25),
            daily_used=access_data.get("daily_used", 0),
            daily_remaining=access_data.get("daily_remaining", 25),
            days_remaining=access_data.get("days_remaining"),
            expires_at=access_data.get("expires_at"),
        )
        return AgentRegisterResponse(
            api_key=response["api_key"],
            claim_url=response["claim_url"],
            claim_code=response["claim_code"],
            agent_name=response["agent_name"],
            access=access,
            message=response.get("message", ""),
        )

    def agent_status(self) -> Dict[str, Any]:
        """Get current agent status."""
        return self._get("/api/v1/agents/me")

    def resend_claim(self) -> Dict[str, Any]:
        """Resend agent claim link."""
        return self._post("/api/v1/agents/resend-claim", {})

    # =========================================================================
    # HTTP Request Methods
    # =========================================================================

    def _get(self, path: str) -> Dict[str, Any]:
        """Make GET request."""
        return self._request("GET", path)

    def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Make POST request."""
        return self._request("POST", path, json=payload)

    def _delete(self, path: str) -> Dict[str, Any]:
        """Make DELETE request."""
        return self._request("DELETE", path)

    def _request(
        self,
        method: str,
        path: str,
        **kwargs
    ) -> Dict[str, Any]:
        """Make HTTP request to API."""
        try:
            response = self._client.request(method, path, **kwargs)
            response.raise_for_status()
            return response.json()
        except httpx.HTTPStatusError as e:
            raise UnSearchAPIError(
                f"API request failed: {e.response.status_code}",
                status_code=e.response.status_code,
                response=e.response.text
            )
        except httpx.RequestError as e:
            raise UnSearchError(f"Request failed: {str(e)}")
    
    def close(self):
        """Close the HTTP client."""
        self._client.close()
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.close()


class AsyncUnSearchClient:
    """
    Async UnSearch API client with full backend capabilities.
    
    Same interface as UnSearchClient but with async methods.
    
    Example:
        >>> async with AsyncUnSearchClient(api_key="your-key") as client:
        ...     response = await client.search("What is Python?")
    """
    
    DEFAULT_BASE_URL = "https://api.unsearch.dev"
    
    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: Optional[str] = None,
        timeout: float = 30.0,
        zero_retention: bool = False
    ):
        self.api_key = api_key or os.environ.get("UNSEARCH_API_KEY")
        if not self.api_key:
            raise UnSearchError(
                "API key required. Pass api_key or set UNSEARCH_API_KEY environment variable."
            )
        
        self.base_url = (base_url or os.environ.get("UNSEARCH_BASE_URL") or self.DEFAULT_BASE_URL).rstrip("/")
        self.timeout = timeout
        self.zero_retention = zero_retention
        
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=timeout,
            headers=self._get_headers()
        )
    
    def _get_headers(self) -> Dict[str, str]:
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json",
            "User-Agent": "unsearch-python/0.1.0"
        }
        if self.zero_retention:
            headers["X-Zero-Retention"] = "true"
        return headers

    # Agent API
    async def search(self, query: str, **kwargs) -> SearchResponse:
        """Async search - same parameters as sync version."""
        payload = {"query": query, **kwargs}
        response = await self._post("/api/v1/agent/search", payload)
        results = [
            SearchResult(
                title=r["title"], url=r["url"], content=r["content"],
                score=r.get("score", 0.0), raw_content=r.get("raw_content")
            )
            for r in response.get("results", [])
        ]
        return SearchResponse(
            query=response["query"], results=results,
            answer=response.get("answer"), images=response.get("images", []),
            response_time=response.get("response_time", 0.0),
            model_used=response.get("model_used"),
            query_complexity=response.get("query_complexity")
        )

    async def extract(self, urls: List[str], **kwargs) -> Dict[str, Any]:
        """Async extract."""
        return await self._post("/api/v1/agent/extract", {"urls": urls, **kwargs})

    async def research(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async research."""
        return await self._post("/api/v1/agent/research", {"query": query, **kwargs})

    async def qna_search(self, query: str, **kwargs) -> str:
        """Async Q&A search."""
        response = await self.search(query, include_answer=True, **kwargs)
        return response.answer or ""

    # Core Search API
    async def core_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async core search."""
        return await self._post("/api/v1/search/", {"query": query, **kwargs})

    async def batch_search(self, queries: List[str], **kwargs) -> Dict[str, Any]:
        """Async batch search."""
        return await self._post("/api/v1/search/batch", {"queries": queries, **kwargs})

    async def list_engines(self) -> Dict[str, Any]:
        """Async list engines."""
        return await self._get("/api/v1/search/engines")

    # RAG API
    async def rag_research(self, topic: str, **kwargs) -> Dict[str, Any]:
        """Async RAG research."""
        return await self._post("/api/v1/rag/research", {"topic": topic, **kwargs})

    async def rag_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async RAG search."""
        return await self._post("/api/v1/rag/search", {"query": query, **kwargs})

    async def semantic_search(self, corpus_id: str, query: str, **kwargs) -> Dict[str, Any]:
        """Async semantic search."""
        return await self._post("/api/v1/rag/semantic-search", {"corpus_id": corpus_id, "query": query, **kwargs})

    async def image_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async image search."""
        return await self._post("/api/v1/rag/images", {"query": query, **kwargs})

    # Neural API
    async def neural_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async neural search."""
        return await self._post("/api/v1/neural/search", {"query": query, **kwargs})

    async def find_similar(self, **kwargs) -> Dict[str, Any]:
        """Async find similar."""
        return await self._post("/api/v1/neural/similar", kwargs)

    async def extract_highlights(self, query: str, content: str, **kwargs) -> Dict[str, Any]:
        """Async extract highlights."""
        return await self._post("/api/v1/neural/highlights", {"query": query, "content": content, **kwargs})

    async def predictive_search(self, **kwargs) -> Dict[str, Any]:
        """Async predictive search."""
        return await self._post("/api/v1/neural/predictive", kwargs)

    # Monitoring API
    async def create_monitor(self, topic: str, **kwargs) -> Dict[str, Any]:
        """Async create monitor."""
        return await self._post("/api/v1/monitor/topics", {"topic": topic, **kwargs})

    async def list_monitors(self) -> List[Dict[str, Any]]:
        """Async list monitors."""
        return await self._get("/api/v1/monitor/topics")

    async def get_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Async get monitor."""
        return await self._get(f"/api/v1/monitor/topics/{monitor_id}")

    async def pause_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Async pause monitor."""
        return await self._post(f"/api/v1/monitor/topics/{monitor_id}/pause", {})

    async def resume_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Async resume monitor."""
        return await self._post(f"/api/v1/monitor/topics/{monitor_id}/resume", {})

    async def delete_monitor(self, monitor_id: str) -> Dict[str, Any]:
        """Async delete monitor."""
        return await self._delete(f"/api/v1/monitor/topics/{monitor_id}")

    # Verification API
    async def verify_claim(self, claim: str, **kwargs) -> Dict[str, Any]:
        """Async verify claim."""
        return await self._post("/api/v1/verify/claim", {"claim": claim, **kwargs})

    async def check_source_credibility(self, url: str) -> Dict[str, Any]:
        """Async check source credibility."""
        return await self._post("/api/v1/verify/source", {"url": url})

    async def batch_verify(self, claims: List[str]) -> Dict[str, Any]:
        """Async batch verify."""
        return await self._post("/api/v1/verify/batch", {"claims": claims})

    async def get_batch_verification_status(self, job_id: str) -> Dict[str, Any]:
        """Async get batch verification status."""
        return await self._get(f"/api/v1/verify/batch/{job_id}")

    # Enhanced API
    async def enhanced_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async enhanced search."""
        return await self._post("/api/v1/enhanced/search", {"query": query, **kwargs})

    async def enhanced_scrape(self, urls: List[str], **kwargs) -> Dict[str, Any]:
        """Async enhanced scrape."""
        return await self._post("/api/v1/enhanced/scrape", {"urls": urls, **kwargs})

    async def enhanced_features(self) -> Dict[str, Any]:
        """Async list enhanced features."""
        return await self._get("/api/v1/enhanced/features")

    async def extract_tables(self, html_content: str, **kwargs) -> Dict[str, Any]:
        """Async extract tables."""
        return await self._post("/api/v1/enhanced/extract-tables", {"html_content": html_content, **kwargs})

    async def chunk_content(self, text: str, **kwargs) -> Dict[str, Any]:
        """Async chunk content."""
        return await self._post("/api/v1/enhanced/chunk-content", {"text": text, **kwargs})

    async def discover_urls(self, base_url: str, **kwargs) -> Dict[str, Any]:
        """Async discover URLs."""
        return await self._post("/api/v1/enhanced/discover-urls", {"base_url": base_url, **kwargs})

    async def enhanced_performance(self) -> Dict[str, Any]:
        """Async enhanced performance metrics."""
        return await self._get("/api/v1/enhanced/performance")

    # Knowledge Graph API
    async def knowledge_extract(self, text: str, **kwargs) -> Dict[str, Any]:
        """Async extract entities and relationships."""
        return await self._post("/api/v1/knowledge/extract", {"text": text, **kwargs})

    async def knowledge_search(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async search knowledge graph."""
        return await self._post("/api/v1/knowledge/search", {"query": query, **kwargs})

    async def knowledge_people(self, query: str, **kwargs) -> Dict[str, Any]:
        """Async search for people."""
        return await self._post("/api/v1/knowledge/people", {"query": query, **kwargs})

    async def knowledge_get_entity(self, entity_id: str) -> Dict[str, Any]:
        """Async get entity."""
        return await self._get(f"/api/v1/knowledge/entities/{entity_id}")

    async def knowledge_graph(self, limit: int = 100) -> Dict[str, Any]:
        """Async get knowledge graph."""
        return await self._get(f"/api/v1/knowledge/graph?limit={limit}")

    async def knowledge_delete_entity(self, entity_id: str) -> Dict[str, Any]:
        """Async delete entity."""
        return await self._delete(f"/api/v1/knowledge/entities/{entity_id}")

    # Agent Registration API
    async def register_agent(self, name: str, **kwargs) -> Dict[str, Any]:
        """Async register agent."""
        return await self._post("/api/v1/agents/register", {"name": name, **kwargs})

    async def agent_status(self) -> Dict[str, Any]:
        """Async get agent status."""
        return await self._get("/api/v1/agents/me")

    async def resend_claim(self) -> Dict[str, Any]:
        """Async resend claim."""
        return await self._post("/api/v1/agents/resend-claim", {})

    # HTTP Methods
    async def _get(self, path: str) -> Dict[str, Any]:
        return await self._request("GET", path)

    async def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
        return await self._request("POST", path, json=payload)

    async def _delete(self, path: str) -> Dict[str, Any]:
        return await self._request("DELETE", path)

    async def _request(self, method: str, path: str, **kwargs) -> Dict[str, Any]:
        try:
            response = await self._client.request(method, path, **kwargs)
            response.raise_for_status()
            return response.json()
        except httpx.HTTPStatusError as e:
            raise UnSearchAPIError(
                f"API request failed: {e.response.status_code}",
                status_code=e.response.status_code,
                response=e.response.text
            )
        except httpx.RequestError as e:
            raise UnSearchError(f"Request failed: {str(e)}")

    async def close(self):
        await self._client.aclose()

    async def __aenter__(self):
        return self

    async def __aexit__(self, *args):
        await self.close()
