from __future__ import annotations

import asyncio
import logging
from typing import Any

import httpx

logger = logging.getLogger("uvicorn.error")

CIN7_DEFAULT_BASE_URL = "https://api.cin7.com/api"


class Cin7ApiError(RuntimeError):
    pass


class Cin7Client:
    def __init__(self, username: str, password: str, base_url: str | None = None):
        self.username = username
        self.password = password
        self.base_url = (base_url or CIN7_DEFAULT_BASE_URL).rstrip("/")

    async def _get(self, endpoint: str, params: dict | None = None) -> Any:
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        # ponytail: 429 retry with backoff — Cin7 limits 3/s 60/min 5000/day
        for attempt in range(5):
            async with httpx.AsyncClient(timeout=60) as client:
                response = await client.get(
                    url, params=params,
                    auth=(self.username, self.password),
                )
            if response.status_code == 429:
                wait = 2 ** attempt * 5  # 5s, 10s, 20s, 40s, 80s
                logger.warning("[cin7] 429 rate limit, retry %d/5 after %ds", attempt + 1, wait)
                await asyncio.sleep(wait)
                continue
            if response.status_code == 404:
                return {}
            if response.status_code >= 400:
                raise Cin7ApiError(f"Cin7 API error {response.status_code}: {response.text[:2000]}")
            return response.json()
        raise Cin7ApiError("Cin7 API error 429: rate limit exceeded after 5 retries")

    async def _get_all_pages(self, endpoint: str, rows: int = 250, extra_params: dict | None = None) -> list[dict]:
        all_items: list[dict] = []
        page = 1
        while True:
            params = {"page": page, "rows": rows, **(extra_params or {})}
            data = await self._get(endpoint, params)
            if isinstance(data, dict):
                items = data.get("d", []) if data else []
            elif isinstance(data, list):
                items = data
            else:
                items = []
            if not items:
                break
            all_items.extend(items)
            if len(items) < rows:
                break
            page += 1
            # ponytail: stay under Cin7's 3 req/s limit; ceil(1/3) + small buffer
            await asyncio.sleep(0.5)
        return all_items

    async def get_products(self) -> list[dict]:
        return await self._get_all_pages("v1/Products")

    async def get_categories(self) -> list[dict]:
        return await self._get_all_pages("v1/ProductCategories")

    async def get_brands(self) -> list[dict]:
        return await self._get_all_pages("v1/Branches")

    async def get_contacts(self, contact_type: str | None = None) -> list[dict]:
        all_contacts = await self._get_all_pages("v1/Contacts")
        if contact_type:
            return [c for c in all_contacts if str(c.get("type", "")) == contact_type]
        return all_contacts

    async def test_connection(self) -> bool:
        try:
            await self._get("v1/Products", {"page": 1, "rows": 1})
            return True
        except Exception as e:
            logger.warning("[cin7] Connection test failed: %s", e)
            return False
