import aiohttp
from typing import Optional
from typing import List, Optional, Literal


class RobloxClient:
    """
    Minimal async Roblox client with support for configuring gamepasses,
    inspired by noblox.js's configureGamePass.
    """

    def __init__(self, security_cookie: str):
        # Allow both "cookie" and ".ROBLOSECURITY=cookie"
        if ".ROBLOSECURITY" in security_cookie:
            self.cookie = security_cookie.split("=", 1)[1].strip()
        else:
            self.cookie = security_cookie.strip()

        self._xcsrf: Optional[str] = None
        self._session: Optional[aiohttp.ClientSession] = None

    async def _ensure_session(self) -> aiohttp.ClientSession:
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession(
                cookies={".ROBLOSECURITY": self.cookie}
            )
        return self._session

    async def _refresh_xcsrf(self) -> None:
        """Fetch a fresh X-CSRF token using the logout request."""
        session = await self._ensure_session()
        async with session.post("https://auth.roblox.com/v2/logout") as resp:
            token = resp.headers.get("x-csrf-token")
            if not token:
                text = await resp.text()
                raise RuntimeError(f"Failed to obtain X-CSRF token: {resp.status} {text}")
            self._xcsrf = token

    async def _request(self, method: str, url: str, **kwargs) -> aiohttp.ClientResponse:
        session = await self._ensure_session()

        if self._xcsrf is None:
            await self._refresh_xcsrf()

        headers = kwargs.pop("headers", {})
        headers["x-csrf-token"] = self._xcsrf

        resp = await session.request(method, url, headers=headers, **kwargs)

        # Handle expired CSRF
        if resp.status == 403:
            await resp.release()
            await self._refresh_xcsrf()
            headers["x-csrf-token"] = self._xcsrf
            resp = await session.request(method, url, headers=headers, **kwargs)

        return resp

    async def update_gamepass_info(
        self,
        gamepass_id: int,
        name: str,
        description: str = "",
        icon_file: Optional[bytes] = None,
        icon_filename: str = "icon.png",
        icon_content_type: str = "image/png",
    ) -> dict:

        url = f"https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}/details"
        from aiohttp import FormData

        form = FormData()
        form.add_field("Name", name)
        form.add_field("Description", description or "")

        if icon_file is not None:
            form.add_field("File", icon_file, filename=icon_filename, content_type=icon_content_type)

        resp = await self._request("POST", url, data=form)
        text = await resp.text()

        if resp.status != 200:
            raise RuntimeError(f"Failed to update gamepass info {gamepass_id}: {resp.status} {text}")

        try:
            return await resp.json()
        except Exception:
            return {"raw": text}

    async def update_gamepass_price(self, gamepass_id: int, price: int | bool) -> dict:

        url = f"https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}/details"
        from aiohttp import FormData

        # Price handling
        numeric_price = 0 if isinstance(price, bool) else int(price)
        is_for_sale = numeric_price > 0

        form = FormData()
        form.add_field("IsForSale", str(is_for_sale).lower())
        form.add_field("Price", str(max(numeric_price, 0)))

        resp = await self._request("POST", url, data=form)
        text = await resp.text()

        if resp.status != 200:
            if resp.status == 403:
                raise PermissionError(f"You do not have permission to edit this gamepass {gamepass_id}.")
            raise RuntimeError(f"Failed to update gamepass price {gamepass_id}: {resp.status} {text}")

        try:
            return await resp.json()
        except Exception:
            return {"raw": text}

    async def configure_gamepass(
        self,
        gamepass_id: int,
        name: str = "",
        description: str = "",
        price: Optional[int | bool] = None,
        icon_path: Optional[str] = None,
    ) -> dict:

        icon_bytes = None
        if icon_path:
            with open(icon_path, "rb") as f:
                icon_bytes = f.read()

        result = {
            "gamePassId": gamepass_id,
            "name": name,
            "description": description,
            "price": price,
            "iconChanged": bool(icon_path),
        }

        # Step 1 — update info unless name is empty
        if name != "":
            result["info"] = await self.update_gamepass_info(
                gamepass_id,
                name=name,
                description=description,
                icon_file=icon_bytes,
            )

        # Step 2 — update price
        if isinstance(price, (bool, int)):
            result["priceResponse"] = await self.update_gamepass_price(gamepass_id, price)

        result["status"] = "success"
        return result

    async def close(self) -> None:
        """Close the underlying aiohttp session safely on Windows."""
        if self._session and not self._session.closed:
            try:
                await self._session.close()
            except RuntimeError:
                # Windows asyncio cleanup bug — ignore
                pass

    async def __aenter__(self) -> "RobloxClient":
        await self._ensure_session()
        return self

    async def __aexit__(self, exc_type, exc, tb) -> None:
        await self.close()

async def get_group_transactions(
    self,
    group_id: int,
    transaction_type: Optional[Literal[
        "Sale", "Purchase", "AffiliateSale", 
        "DevEx", "GroupPayout", "AdImpressionPayout"
    ]] = "Sale",
    limit: Optional[int] = 10,
    sort_order: Optional[Literal["Asc", "Desc"]] = "Asc",
    cursor: Optional[str] = None
) -> List[dict]:
    """
    🔐 Get a group's transactions (matches noblox.js).
    
    :param group_id: Group ID.
    :param transaction_type: Filter (default: "Sale").
    :param limit: Max results per page (10-100).
    :param sort_order: "Asc"/"Desc".
    :param cursor: Pagination cursor.
    """
    url = f"https://economy.roblox.com/v2/groups/{group_id}/transactions"
    
    params: dict[str, str] = {
        "transactionType": transaction_type,
        "sortOrder": sort_order,
        "limit": str(limit)
    }
    if cursor:
        params["cursor"] = cursor
    
    resp = await self._request("GET", url, params=params)
    if resp.status != 200:
        text = await resp.text()
        raise RuntimeError(f"Transactions failed [{group_id}]: {resp.status} {text}")
    
    data = await resp.json()
    return data.get("data", [])
