"""Official Python client for the MetaScrape API."""

import json
import urllib.request
import urllib.error
import urllib.parse

from .exceptions import MetaScrapeError

DEFAULT_BASE_URL = "https://api.shanecode.org"


class MetaScrape:
    """MetaScrape API client.

    Args:
        api_key: Your MetaScrape API key (starts with msk_).
        base_url: API base URL. Defaults to https://api.shanecode.org.
    """

    def __init__(self, api_key, base_url=DEFAULT_BASE_URL):
        if not api_key:
            raise ValueError("MetaScrape: api_key is required")
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")

    def extract(self, url):
        """Extract metadata from a URL.

        Args:
            url: The URL to extract metadata from.

        Returns:
            dict: Metadata object (title, description, og_image, etc.)

        Raises:
            MetaScrapeError: If the API returns a non-2xx response.
        """
        if not url:
            raise ValueError("MetaScrape: url is required")
        endpoint = f"{self._base_url}/v1/extract?url={urllib.parse.quote(url, safe='')}"
        body = self._request("GET", endpoint, headers=self._auth_headers())
        return body["data"]

    def usage(self):
        """Get current month API usage.

        Returns:
            dict: Usage object with plan, used, limit, remaining.

        Raises:
            MetaScrapeError: If the API returns a non-2xx response.
        """
        endpoint = f"{self._base_url}/v1/usage"
        body = self._request("GET", endpoint, headers=self._auth_headers())
        return body["data"]

    @classmethod
    def signup(cls, email, password, base_url=DEFAULT_BASE_URL):
        """Create a new account.

        Args:
            email: Account email address.
            password: Account password.
            base_url: API base URL.

        Returns:
            dict: Account creation response.

        Raises:
            MetaScrapeError: If the API returns a non-2xx response.
        """
        endpoint = f"{base_url.rstrip('/')}/v1/signup"
        return cls._request_static(
            "POST", endpoint,
            data={"email": email, "password": password},
        )

    @classmethod
    def login(cls, email, password, base_url=DEFAULT_BASE_URL):
        """Login and retrieve your API key.

        Args:
            email: Account email address.
            password: Account password.
            base_url: API base URL.

        Returns:
            dict: Login response containing api_key.

        Raises:
            MetaScrapeError: If the API returns a non-2xx response.
        """
        endpoint = f"{base_url.rstrip('/')}/v1/login"
        return cls._request_static(
            "POST", endpoint,
            data={"email": email, "password": password},
        )

    def _auth_headers(self):
        return {"X-API-Key": self._api_key}

    def _request(self, method, url, headers=None, data=None):
        return self.__class__._do_request(method, url, headers=headers, data=data)

    @classmethod
    def _request_static(cls, method, url, headers=None, data=None):
        return cls._do_request(method, url, headers=headers, data=data)

    @staticmethod
    def _do_request(method, url, headers=None, data=None):
        req_headers = headers or {}
        body_bytes = None
        if data is not None:
            body_bytes = json.dumps(data).encode("utf-8")
            req_headers["Content-Type"] = "application/json"

        req = urllib.request.Request(
            url, data=body_bytes, headers=req_headers, method=method
        )

        try:
            with urllib.request.urlopen(req) as resp:
                resp_body = json.loads(resp.read().decode("utf-8"))
                return resp_body
        except urllib.error.HTTPError as e:
            try:
                err_body = json.loads(e.read().decode("utf-8"))
                msg = err_body.get("error") or err_body.get("message") or e.reason
            except Exception:
                msg = e.reason
            raise MetaScrapeError(e.code, msg) from None
