"""Vanefeed Python SDK — one file, no dependencies (Python 3.9+).

    from vanefeed import Vanefeed
    sw = Vanefeed("sk_live_...")                  # or env SW_KEY
    print(sw.profile("youtube", "@TED")["data"]["followers"])
    for post in sw.iter_posts("x", "NASA", limit=20, max_pages=3):
        print(post["text"])
    print(sw.last_cost, sw.last_remaining)        # credits used / left

Every call returns the API's JSON. Errors raise VanefeedError with the
API's error code, message and request id. Rate limits (429) are retried
automatically using Retry-After.
"""
from __future__ import annotations

import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Iterator

__version__ = "1.0.0"
PLATFORMS = ("youtube", "x", "reddit", "telegram", "web", "tiktok", "instagram", "facebook")


class VanefeedError(Exception):
    def __init__(self, status: int, code: str, message: str, request_id: str | None = None):
        super().__init__(f"{status} {code}: {message}")
        self.status, self.code, self.message, self.request_id = status, code, message, request_id


class Vanefeed:
    def __init__(self, api_key: str | None = None, base_url: str | None = None,
                 timeout: float = 30.0, max_retries: int = 2):
        self.api_key = api_key or os.environ.get("SW_KEY", "")
        if not self.api_key:
            raise ValueError("pass api_key or set SW_KEY")
        self.base_url = (base_url or os.environ.get("SW") or "https://api.vanefeed.com").rstrip("/")
        self.timeout, self.max_retries = timeout, max_retries
        self.last_cost: int | None = None
        self.last_remaining: int | None = None
        self.last_request_id: str | None = None

    # -- transport --------------------------------------------------------
    def _get(self, path: str, params: dict | None = None) -> dict:
        query = {k: v for k, v in (params or {}).items() if v is not None and v != ""}
        url = f"{self.base_url}{path}" + (f"?{urllib.parse.urlencode(query)}" if query else "")
        req = urllib.request.Request(url, headers={
            "Authorization": f"Bearer {self.api_key}", "Accept": "application/json",
            "User-Agent": f"vanefeed-python/{__version__}"})
        for attempt in range(self.max_retries + 1):
            try:
                with urllib.request.urlopen(req, timeout=self.timeout) as r:
                    self._meta(r.headers)
                    return json.loads(r.read().decode("utf-8"))
            except urllib.error.HTTPError as e:
                self._meta(e.headers)
                try:
                    body = json.loads(e.read().decode("utf-8"))
                except ValueError:
                    body = {}
                if e.code == 429 and attempt < self.max_retries:
                    time.sleep(min(60.0, float(e.headers.get("Retry-After") or 2)))
                    continue
                raise VanefeedError(e.code, body.get("error", "error"),
                                    body.get("message", str(e)), self.last_request_id) from None
        raise VanefeedError(429, "rate_limit_exceeded", "gave up after retries", self.last_request_id)

    def _meta(self, headers) -> None:
        def num(h):
            v = headers.get(h)
            return int(v) if v and v.isdigit() else None
        self.last_cost, self.last_remaining = num("X-Credits-Cost"), num("X-Credits-Remaining")
        self.last_request_id = headers.get("X-Request-Id")

    # -- on-demand data ------------------------------------------------------
    def profile(self, platform: str, handle: str, **params) -> dict:
        return self._get(f"/v1/{platform}/profile", {"handle": handle, **params})

    def posts(self, platform: str, handle: str | None = None, *, url: str | None = None,
              limit: int | None = None, cursor: str | None = None, **params) -> dict:
        return self._get(f"/v1/{platform}/posts",
                         {"handle": handle, "url": url, "limit": limit, "cursor": cursor, **params})

    def post(self, platform: str, id: str, **params) -> dict:
        return self._get(f"/v1/{platform}/post", {"id": id, **params})

    def comments(self, platform: str, id: str | None = None, *, limit: int | None = None,
                 cursor: str | None = None, **params) -> dict:
        return self._get(f"/v1/{platform}/comments", {"id": id, "limit": limit, "cursor": cursor, **params})

    def search(self, platform: str, q: str | None = None, *, limit: int | None = None,
               cursor: str | None = None, **params) -> dict:
        return self._get(f"/v1/{platform}/search", {"q": q, "limit": limit, "cursor": cursor, **params})

    def _iter(self, method, first_kwargs: dict, max_pages: int) -> Iterator[dict]:
        page = method(**first_kwargs)
        for _ in range(max_pages):
            yield from page.get("data") or []
            cursor = page.get("next_cursor")
            if not cursor:
                return
            page = method(**{"platform": first_kwargs["platform"], "cursor": cursor,
                             "limit": first_kwargs.get("limit")})

    def iter_posts(self, platform: str, handle: str, *, limit: int = 20, max_pages: int = 5):
        """Yield posts across pages (each page costs credits)."""
        return self._iter(self.posts, {"platform": platform, "handle": handle, "limit": limit}, max_pages)

    def iter_comments(self, platform: str, id: str, *, limit: int = 50, max_pages: int = 5):
        return self._iter(self.comments, {"platform": platform, "id": id, "limit": limit}, max_pages)

    def iter_search(self, platform: str, q: str, *, limit: int = 20, max_pages: int = 5, **params):
        return self._iter(self.search, {"platform": platform, "q": q, "limit": limit, **params}, max_pages)

    # -- tracked data, account, status -----------------------------------------
    def search_mentions(self, **params) -> dict:
        return self._get("/v1/mentions/search", params)

    def usage(self) -> dict:
        return self._get("/v1/usage")

    def status(self) -> dict:
        return self._get("/v1/status")

    def price(self, path: str, **params) -> dict:
        """Free price check: what would this call cost? (dry_run)"""
        return self._get(path, {**params, "dry_run": "true"})
