"""Dependency-free OrbitSearch client for Python 3.10+."""
import json, time
from urllib.parse import urlencode, urljoin, urlparse
from urllib.request import Request, urlopen
from urllib.error import HTTPError

class OrbitSearchError(Exception):
    def __init__(self, message, status=None, code=None, retryable=False):
        super().__init__(message); self.status=status; self.code=code; self.retryable=retryable

class OrbitSearch:
    def __init__(self, api_key, base_url="https://search.rviv.in"):
        if not api_key: raise ValueError("OrbitSearch API key is required")
        self.api_key, self.base_url = api_key, base_url.rstrip("/")
    def request(self, path, **params):
        query=urlencode({k:v for k,v in params.items() if v is not None})
        url=urljoin(self.base_url, path)+(f"?{query}" if query else "")
        request=Request(url, headers={"X-API-Key":self.api_key,"Accept":"application/json"})
        try:
            with urlopen(request, timeout=30) as response: return json.load(response)
        except HTTPError as error:
            try: body=json.load(error)
            except Exception: body={}
            value=body.get("error", {})
            message=value.get("message") if isinstance(value,dict) else value
            raise OrbitSearchError(message or f"HTTP {error.code}",error.code,body.get("error_code") or (value.get("code") if isinstance(value,dict) else None),body.get("retryable",False)) from error
    def search(self, query, **options): return self.request("/search.json",q=query,engine="google",**options)
    def images(self, query, **options): return self.request("/search.json",q=query,engine="google_images",**options)
    def news(self, query, **options): return self.request("/search.json",q=query,engine="google_news",**options)
    def maps(self, query, **options): return self.request("/search.json",q=query,engine="google_maps",**options)
    def videos(self, query, **options): return self.request("/search.json",q=query,engine="google_videos",**options)
    def social(self, query, **options): return self.request("/search.json",q=query,engine="social",**options)
    def shopping(self, query, **options): return self.request("/search.json",q=query,engine="shopping",**options)
    def flights(self, query, **options): return self.request("/search.json",q=query,engine="flights",**options)
    def hotels(self, query, **options): return self.request("/search.json",q=query,engine="hotels",**options)
    def reviews(self, query, **options): return self.request("/search.json",q=query,engine="reviews",**options)
    def autocomplete(self, query): return self.request("/api/v1/autocomplete",q=query)
    def trends(self, geo="IN"): return self.request("/api/v1/trends",geo=geo)
    def extract(self, url, **options): return self.request("/api/v1/extract",url=url,**options)
    def media(self, engine, query, **options): return self.request("/api/v1/media",engine=engine,q=query,**options)
    def free_images(self, query, **options): return self.media("free_images",query,**options)
    def free_videos(self, query, **options): return self.media("free_videos",query,**options)
    def free_music(self, query, **options): return self.media("free_music",query,**options)
    def sound_effects(self, query, **options): return self.media("sound_effects",query,**options)
    def trending_sfx(self, query, **options): return self.media("trending_sfx",query,**options)
    def gifs(self, query, **options): return self.media("gifs",query,**options)
    def stickers(self, query, **options): return self.media("stickers",query,**options)
    def icons(self, query, **options): return self.media("icons",query,**options)
    def ebooks(self, query, **options): return self.media("ebooks",query,**options)
    def fonts(self, query, **options): return self.media("fonts",query,**options)
    def models_3d(self, query, **options): return self.media("models_3d",query,**options)
    def tamil_memes(self, query, **options): return self.media("tamil_memes",query,**options)
    def tamil_video_memes(self, query, **options): return self.media("tamil_video_memes",query,**options)
    def cinema_memes(self, query, **options): return self.media("cinema_memes",query,**options)
    def everything(self, query, **options): return self.request("/api/v1/everything",q=query,**options)
    def world_cinema_memes(self, query, **options): return self.media("world_cinema_memes",query,**options)
    def world_cinema_clips(self, query, **options): return self.media("world_cinema_clips",query,**options)
    def instagram_posts(self, query, **options): return self.media("instagram_posts",query,**options)
    def x_posts(self, query, **options): return self.media("x_posts",query,**options)
    def usage(self): return self.request("/usage")
    def archived(self, search_id): return self.request(f"/searches/{search_id}.json")
    def search_async(self, query, **options):
        result=self.request("/search.json",**{"q":query,"async":"true",**options})
        while result.get("search_metadata",{}).get("status")=="Processing":
            time.sleep(.5); result=self.request(urlparse(result["search_metadata"]["json_endpoint"]).path)
        if result.get("search_metadata",{}).get("status")=="Error": raise OrbitSearchError(result.get("error","Async search failed"),502,"search_failed",True)
        return result
    def download(self, download_url):
        with urlopen(download_url,timeout=30) as response: return response.read(),response.headers.get_content_type()
