"""
Notion API wrapper with retry logic and rate limit handling.
"""

import asyncio
import sys

import aiohttp

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

NOTION_API_BASE = "https://api.notion.com/v1"
NOTION_VERSION = "2022-06-28"


class NotionAPI:
    """Async Notion API client with automatic rate limit handling."""

    def __init__(self, token: str, max_retries: int = 5):
        self._token = token
        self._max_retries = max_retries
        self._headers = {
            "Authorization": f"Bearer {token}",
            "Notion-Version": NOTION_VERSION,
            "Content-Type": "application/json",
        }
        self._session: aiohttp.ClientSession | None = None

    async def __aenter__(self):
        self._session = aiohttp.ClientSession()
        return self

    async def __aexit__(self, *exc):
        if self._session:
            await self._session.close()

    async def _request(
        self,
        method: str,
        path: str,
        json_data: dict | None = None,
    ) -> dict | None:
        """Make an API request with exponential backoff on rate limits."""
        if not self._session:
            raise RuntimeError("Use 'async with NotionAPI(...)' as context manager")

        url = f"{NOTION_API_BASE}{path}"

        for attempt in range(self._max_retries):
            try:
                kwargs = {"headers": self._headers}
                if json_data is not None:
                    kwargs["json"] = json_data

                async with self._session.request(method, url, **kwargs) as resp:
                    if resp.status == 200:
                        return await resp.json()

                    if resp.status == 429:
                        retry_after = resp.headers.get("Retry-After")
                        if retry_after:
                            wait = float(retry_after)
                        else:
                            wait = (2**attempt) + 1
                        print(f"  [RATE LIMIT] Waiting {wait:.0f}s...")
                        await asyncio.sleep(wait)
                        continue

                    text = await resp.text()
                    print(f"  [ERROR] {resp.status}: {text[:200]}")
                    return None

            except (aiohttp.ClientError, asyncio.TimeoutError) as e:
                print(f"  [ERROR] {e}")
                await asyncio.sleep(2**attempt)

        return None

    async def get(self, path: str) -> dict | None:
        return await self._request("GET", path)

    async def post(self, path: str, json_data: dict | None = None) -> dict | None:
        return await self._request("POST", path, json_data)

    async def query_database(self, database_id: str) -> list[dict]:
        """Fetch all pages from a database, handling pagination."""
        pages = []
        has_more = True
        next_cursor = None

        while has_more:
            payload: dict = {"page_size": 100}
            if next_cursor:
                payload["start_cursor"] = next_cursor

            result = await self.post(
                f"/databases/{database_id}/query",
                payload,
            )
            if not result:
                break

            pages.extend(result.get("results", []))
            has_more = result.get("has_more", False)
            next_cursor = result.get("next_cursor")

            print(f"  Fetched {len(pages)} pages...")
            await asyncio.sleep(0.1)

        return pages

    async def get_block_children(self, block_id: str) -> list[dict]:
        """Fetch all child blocks of a block, handling pagination."""
        blocks = []
        has_more = True
        next_cursor = None

        while has_more:
            path = f"/blocks/{block_id}/children?page_size=100"
            if next_cursor:
                path += f"&start_cursor={next_cursor}"

            result = await self.get(path)
            if not result:
                break

            blocks.extend(result.get("results", []))
            has_more = result.get("has_more", False)
            next_cursor = result.get("next_cursor")

        return blocks
