"""OnePass trading API client for Python 3.8+. No dependencies.

    from onepass_api import OnePass
    op = OnePass("op_live_...", base="https://<the OnePass site>/api/v1")   # or ONEPASS_API_KEY / ONEPASS_API_BASE
    acct = op.accounts()[0]
    op.order(acct["id"], "market", "NVDA", "long", notional=20000, tp=230, sl=215)
    for event, data in op.stream(["NVDA", "BTC"], account=acct["id"]):
        print(event, data)

Same rules as the trade screen: market orders fill at the live price 250 ms after they arrive,
new orders are limited to 2 a second per account, and markets pause on schedule (see pauses()).
"""
import json
import os
import urllib.error
import urllib.parse
import urllib.request

__all__ = ["OnePass", "OnePassError"]


class OnePassError(Exception):
    def __init__(self, status, code, message):
        super().__init__(f"{status} {code}: {message}")
        self.status, self.code, self.message = status, code, message


class OnePass:
    def __init__(self, key=None, base=None, timeout=15):
        self.key = key or os.environ.get("ONEPASS_API_KEY")
        if not self.key:
            raise ValueError("Pass your API key or set ONEPASS_API_KEY (create one under My accounts -> API).")
        base = base or os.environ.get("ONEPASS_API_BASE")
        if not base:
            raise ValueError("Pass base= (the API address shown on the API docs page) or set ONEPASS_API_BASE.")
        self.base = base.rstrip("/")
        self.timeout = timeout

    def _req(self, method, path, body=None, params=None):
        url = self.base + path + ("?" + urllib.parse.urlencode(params) if params else "")
        data = json.dumps(body).encode() if body is not None else None
        req = urllib.request.Request(url, data=data, method=method, headers={"Authorization": f"Bearer {self.key}", "Content-Type": "application/json"})
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as r:
                return json.loads(r.read() or b"{}")
        except urllib.error.HTTPError as e:
            try:
                err = json.loads(e.read()).get("error", {})
            except Exception:
                err = {}
            raise OnePassError(e.code, err.get("code", "error"), err.get("message", str(e))) from None

    # Accounts
    def accounts(self):
        return self._req("GET", "/accounts")["accounts"]

    def account(self, account_id):
        return self._req("GET", f"/accounts/{account_id}")

    # Orders and positions
    def order(self, account_id, type, symbol, side, notional, price=None, tp=None, sl=None):
        body = {"type": type, "symbol": symbol, "side": side, "notional": notional}
        for k, v in (("price", price), ("tp", tp), ("sl", sl)):
            if v is not None:
                body[k] = v
        return self._req("POST", f"/accounts/{account_id}/orders", body)

    def cancel(self, account_id):
        return self._req("DELETE", f"/accounts/{account_id}/orders")

    def set_brackets(self, account_id, tp="keep", sl="keep"):
        """Move take-profit and stop-loss; pass None to clear one, leave it out to keep it."""
        body = {k: v for k, v in (("tp", tp), ("sl", sl)) if v != "keep"}
        return self._req("PATCH", f"/accounts/{account_id}/position", body)

    def close(self, account_id):
        return self._req("DELETE", f"/accounts/{account_id}/position")

    # Market data
    def markets(self):
        return self._req("GET", "/markets")

    def book(self, symbol, depth=10):
        return self._req("GET", f"/markets/{symbol}/book", params={"depth": depth})

    def candles(self, symbol, tf="1m", limit=300):
        return self._req("GET", f"/markets/{symbol}/candles", params={"tf": tf, "limit": limit})

    def pauses(self):
        return self._req("GET", "/pauses")

    def stream(self, symbols, account=None):
        """Yield (event, data) forever: "quote" and "account" events. Reconnects by itself."""
        params = {"symbols": ",".join(symbols)}
        if account:
            params["account"] = account
        url = self.base + "/stream?" + urllib.parse.urlencode(params)
        while True:
            req = urllib.request.Request(url, headers={"Authorization": f"Bearer {self.key}", "Accept": "text/event-stream"})
            event = None
            with urllib.request.urlopen(req, timeout=60) as r:
                for raw in r:
                    line = raw.decode().rstrip("\n")
                    if line.startswith("event: "):
                        event = line[7:]
                    elif line.startswith("data: ") and event:
                        data = json.loads(line[6:])
                        if event == "reconnect":
                            break
                        yield event, data
