import httpx


class BaseRPCNode:
    def __init__(self, NODE_OPTION):
        self.rpcUser = NODE_OPTION.get("rpcUser")
        self.rpcHost = NODE_OPTION.get("rpcHost")
        self.rpcPassword = NODE_OPTION.get("rpcPassword")
        self.rpcPort = NODE_OPTION.get("rpcPort")

        self.base_url = f"http://{self.rpcHost}:{self.rpcPort}"

        self.client = httpx.AsyncClient(
            base_url=self.base_url,
            auth=(self.rpcUser, self.rpcPassword),
            timeout=5.0
        )

    async def callRpcMethod(self, method, params=None):
        if params is None:
            params = []
        try:
            response = await self.client.post("/", json={
                "jsonrpc": "1.0",
                "id": "testnet",
                "method": method,
                "params": params
            })
            response.raise_for_status()
            return response.json()["result"]

        except httpx.HTTPStatusError as e:
            print("RPC Error:", e.response.text)
            raise
        except Exception as e:
            print("RPC Error:", str(e))
            raise

    async def getblockcount(self):
        return await self.callRpcMethod("getblockcount", [])

    async def getblockhash(self, height):
        return await self.callRpcMethod("getblockhash", [height])

    async def getblock(self, param):
        if isinstance(param, str):
            return await self.callRpcMethod("getblock", [param, 2])
        elif isinstance(param, int):
            hash_ = await self.getblockhash(param)
            return await self.callRpcMethod("getblock", [hash_, 2])
        else:
            raise ValueError("param must be str or int")


# ================= NEXA =================
class NEXARPCNode(BaseRPCNode):

    async def verify_wallet(self, address):
        try:
            await self.callRpcMethod("getreceivedbyaddress", [address])
            return True
        except Exception:
            return False

    async def getblock(self, height):
        return await self.callRpcMethod("getblock", [height, 2])

    async def verify_block(self, height, address):
        block_data = await self.getblock(height)

        for item in block_data["tx"]:
            if len(item["vin"]) == 0:
                addresses = item["vout"][0]["scriptPubKey"]["addresses"]
                if address == addresses[0]:
                    return block_data
                else:
                    return False

    def block(self, block_data):
        tx = block_data["tx"]
        time = block_data["time"]
        hash_ = block_data["hash"]
        height = block_data["height"]

        block_fees = 0
        block_reward = 0

        for item in tx:
            if len(item["vin"]) == 0:
                block_reward = item["sends"]
            else:
                block_fees += item.get("fee", 0)

        return {
            "height": height,
            "hash": hash_,
            "time": time,
            "block_reward": block_reward,
            "block_fees": block_fees
        }


# ================= GRS =================
class GRSRPCNode(BaseRPCNode):

    async def verify_block(self, height, REPORT_ADDRESS):
        block_data = await self.getblock(height)

        for item in block_data["tx"]:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    addr = value["scriptPubKey"].get("address")
                    if addr:
                        if addr == REPORT_ADDRESS:
                            return block_data
                        else:
                            return False

        return False

    def block(self, data):
        hash_ = data["hash"]
        tx = data["tx"]
        height = data["height"]
        time = data["time"]

        reward = 0
        fees = 0

        for item in tx:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    reward += value["value"]
            else:
                fees += item.get("fee", 0)

        return {
            "height": height,
            "hash": hash_,
            "time": time,
            "block_reward": reward,
            "block_fees": fees
        }


# ================= MONA =================
class MONARPCNode(BaseRPCNode):

    async def verify_block(self, height, REPORT_ADDRESS):
        block_data = await self.getblock(height)

        for item in block_data["tx"]:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    addresses = value["scriptPubKey"].get("addresses")
                    if addresses:
                        first = addresses.get("0") if isinstance(addresses, dict) else addresses[0]

                        if first == REPORT_ADDRESS:
                            return block_data
                        else:
                            return False

        return False

    def block(self, data):
        hash_ = data["hash"]
        tx = data["tx"]
        height = data["height"]
        time = data["time"]

        reward = 0

        for item in tx:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    if value["scriptPubKey"].get("addresses"):
                        reward += value["value"]

        return {
            "height": height,
            "hash": hash_,
            "time": time,
            "block_reward": reward,
            "block_fees": None
        }


# ================= DGB =================
class DGBRPCNode(BaseRPCNode):

    async def verify_block(self, height, REPORT_ADDRESS):
        block_data = await self.getblock(height)

        for item in block_data["tx"]:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    addresses = value["scriptPubKey"].get("addresses")
                    if addresses and addresses[0] == REPORT_ADDRESS:
                        return block_data

        return False

    async def verify_block_with_algo(self, height, REPORT_ADDRESS, algorithm):
        block_data = await self.getblock(height)

        if block_data.get("pow_algo") == algorithm:
            for item in block_data["tx"]:
                vin = item["vin"]
                vout = item["vout"]

                if vin[0].get("coinbase"):
                    for value in vout:
                        addresses = value["scriptPubKey"].get("addresses")
                        if addresses and addresses[0] == REPORT_ADDRESS:
                            return block_data

        return False

    def block(self, data):
        hash_ = data["hash"]
        tx = data["tx"]
        height = data["height"]
        time = data["time"]

        reward = 0

        for item in tx:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    if value["scriptPubKey"].get("addresses"):
                        reward += value["value"]

        return {
            "height": height,
            "hash": hash_,
            "time": time,
            "block_reward": reward,
            "block_fees": None
        }


# ================= RXD =================
class RXDRPCNode(BaseRPCNode):

    async def verify_block(self, height, REPORT_ADDRESS):
        block_data = await self.getblock(height)

        for item in block_data["tx"]:
            vin = item["vin"]
            vout = item["vout"]

            if vin[0].get("coinbase"):
                for value in vout:
                    addresses = value["scriptPubKey"].get("addresses")
                    if addresses and addresses[0] == REPORT_ADDRESS:
                        hash_ = await self.getblockhash(height)
                        blockstats = await self.callRpcMethod("getblockstats", [hash_])

                        return {
                            "height": blockstats["height"],
                            "hash": blockstats["blockhash"],
                            "time": blockstats["time"],
                            "block_reward": blockstats["subsidy"] + blockstats["totalfee"],
                            "block_fees": blockstats["totalfee"]
                        }

        return False

    def block(self, data):
        return data


__all__ = [
    "NEXARPCNode",
    "GRSRPCNode",
    "MONARPCNode",
    "DGBRPCNode",
    "RXDRPCNode"
]