import asyncio
import json
from dataclasses import dataclass, field
from typing import Any
from urllib.error import HTTPError
from urllib.request import Request, urlopen

@dataclass
class Result:
    data: Any = None
    error: dict | None = None
    meta: dict = field(default_factory=dict)
    exec_time: float | None = None

class singlebaseClient:
    def __init__(self, api_key: str, api_url: str, bearer: str | None = None):
        self.api_key, self.api_url, self.bearer = api_key, api_url, bearer

    async def call(self, operation: str, payload: dict, options: dict | None = None) -> Result:
        return await asyncio.to_thread(self._call, operation, payload, options)

    def _call(self, operation, payload, options):
        headers = {"Content-Type": "application/json", "X-API-KEY": self.api_key}
        if self.bearer:
            headers["Authorization"] = "Bearer " + self.bearer
        body = {"operation": operation, "payload": payload}
        if options is not None:
            body["options"] = options
        request = Request(self.api_url, data=json.dumps(body).encode(), headers=headers, method="POST")
        try:
            response = urlopen(request, timeout=30)
        except HTTPError as exc:
            response = exc
        with response:
            result = json.load(response)
            if response.status >= 400 and not result.get("error"):
                raise RuntimeError(f"HTTP {response.status} without an API error envelope")
        return Result(result.get("data"), result.get("error"), result.get("meta") or {}, result.get("exec_time"))
