from __future__ import annotations

import base64
import json
from typing import Any

import httpx

from .config import settings
from .models import Network


class NearRPCError(RuntimeError):
    def __init__(self, message: str, *, payload: dict[str, Any] | None = None):
        super().__init__(message)
        self.payload = payload or {}


class NearRPCClient:
    def __init__(self, *, timeout_seconds: float | None = None):
        self._timeout = timeout_seconds or settings.rpc_timeout_seconds

    @staticmethod
    def rpc_url(network: Network) -> str:
        if network == Network.mainnet:
            return settings.mainnet_rpc_url
        if network == Network.testnet:
            return settings.testnet_rpc_url
        raise ValueError(f"Unsupported network: {network}")

    async def _rpc_call(self, network: Network, method: str, params: Any) -> dict[str, Any]:
        rpc_url = self.rpc_url(network)
        payload = {"jsonrpc": "2.0", "id": "near-fastapi", "method": method, "params": params}

        async with httpx.AsyncClient(timeout=self._timeout) as client:
            response = await client.post(rpc_url, json=payload)
            response.raise_for_status()
            data = response.json()

        if "error" in data:
            err = data.get("error", {})
            msg = err.get("message") or "NEAR RPC error"
            raise NearRPCError(msg, payload=err)

        return data.get("result", {})

    async def network_status(self, network: Network) -> dict[str, Any]:
        return await self._rpc_call(network, "status", [])

    async def view_account(self, account_id: str, network: Network) -> dict[str, Any]:
        params = {
            "request_type": "view_account",
            "account_id": account_id,
            "finality": "final",
        }
        return await self._rpc_call(network, "query", params)

    async def access_keys(self, account_id: str, network: Network) -> dict[str, Any]:
        params = {
            "request_type": "view_access_key_list",
            "account_id": account_id,
            "finality": "final",
        }
        return await self._rpc_call(network, "query", params)

    async def contract_code_hash(self, account_id: str, network: Network) -> dict[str, Any]:
        params = {
            "request_type": "view_code",
            "account_id": account_id,
            "finality": "final",
        }
        result = await self._rpc_call(network, "query", params)
        return {
            "account_id": account_id,
            "code_hash": result.get("code_hash"),
            "block_height": result.get("block_height"),
            "block_hash": result.get("block_hash"),
        }

    async def view_function(
        self,
        *,
        account_id: str,
        method_name: str,
        args: dict[str, Any],
        network: Network,
        finality: str = "final",
    ) -> dict[str, Any]:
        args_json = json.dumps(args, separators=(",", ":")).encode("utf-8")
        params = {
            "request_type": "call_function",
            "account_id": account_id,
            "method_name": method_name,
            "args_base64": base64.b64encode(args_json).decode("ascii"),
            "finality": finality,
        }
        result = await self._rpc_call(network, "query", params)

        raw = result.get("result", [])
        decoded = None
        if isinstance(raw, list):
            try:
                raw_bytes = bytes(raw)
                raw_text = raw_bytes.decode("utf-8")
                try:
                    decoded = json.loads(raw_text)
                except json.JSONDecodeError:
                    decoded = raw_text
            except Exception:
                decoded = None

        return {
            "block_height": result.get("block_height"),
            "block_hash": result.get("block_hash"),
            "logs": result.get("logs", []),
            "raw_result": raw,
            "decoded_result": decoded,
        }
