import aiohttp
from aiohttp import FormData
from typing import Optional, List, Literal, Dict, Any


TransactionType = Literal[
    "Sale",
    "Purchase",
    "AffiliateSale",
    "DevEx",
    "GroupPayout",
    "AdImpressionPayout",
]

SortOrder = Literal["Asc", "Desc"]


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[str, Any]:
        url = f"https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}/details"

        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[str, Any]:
        url = f"https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}/details"

        # 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[str, Any]:
        icon_bytes = None
        if icon_path:
            with open(icon_path, "rb") as f:
                icon_bytes = f.read()

        result: Dict[str, Any] = {
            "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 get_group_transactions(
        self,
        group_id: int,
        transaction_type: TransactionType = "Sale",
        limit: int = 10,
        sort_order: SortOrder = "Asc",
        cursor: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        🔐 Get a group's transactions (matches noblox.js getGroupTransactions). [page:0]
        Returns the full response: { "data": [...], "nextPageCursor": ..., "previousPageCursor": ... }.
        """
        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)
        text = await resp.text()

        if resp.status != 200:
            raise RuntimeError(f"Failed to fetch group transactions {group_id}: {resp.status} {text}")

        # resp.json() needs the body available; we already read text so parse manually
        try:
            return await resp.json()
        except Exception:
            return {"raw": text}

    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()
