"""VaultModule — deploy vaults, read vault state, and query NAV on-chain."""

from __future__ import annotations

import os
import secrets
from typing import Optional

from openhedge.constants import resolve_token
from openhedge.exceptions import TransactionError
from openhedge.models.vault import Guardrails, Vault, VaultConfig, VaultNav
from openhedge.modules.base import BaseModule


class VaultModule(BaseModule):
    """On-chain interactions with VaultFactory and TraderVault contracts."""

    # ------------------------------------------------------------------
    # Deploy
    # ------------------------------------------------------------------

    async def deploy(self, config: VaultConfig) -> Vault:
        """Deploy a new TraderVault through the VaultFactory.

        Parameters
        ----------
        config:
            Vault deployment configuration including name, asset, and guardrails.

        Returns
        -------
        A :class:`Vault` snapshot of the newly deployed vault.
        """
        asset_address = resolve_token(config.asset, self._network)

        if config.salt:
            salt = config.salt
        else:
            salt = "0x" + secrets.token_hex(32)

        # VaultFactory.deployVault(string vaultName, address asset,
        #     (uint16, uint16, uint16) guardrails, bytes32 salt)
        calldata = self._encode_call(
            "deployVault(string,address,(uint16,uint16,uint16),bytes32)",
            ["string", "address", "(uint16,uint16,uint16)", "bytes32"],
            [
                config.name,
                asset_address,
                (
                    config.guardrails.max_leverage_bps,
                    config.guardrails.max_drawdown_bps,
                    config.guardrails.max_position_bps,
                ),
                bytes.fromhex(salt[2:]) if salt.startswith("0x") else bytes.fromhex(salt),
            ],
        )

        factory = self._contracts["vault_factory"]
        receipt = await self._send_tx(to=factory, data=calldata)

        # Parse VaultDeployed event to get the vault address
        vault_address = self._parse_vault_deployed(receipt)

        return await self.get(vault_address)

    # ------------------------------------------------------------------
    # Read
    # ------------------------------------------------------------------

    async def get(self, vault_address: str) -> Vault:
        """Read the full on-chain state of a TraderVault.

        Parameters
        ----------
        vault_address:
            The checksummed address of the vault to query.
        """
        # Parallel reads via individual eth_call
        name_data = await self._eth_call(
            vault_address, self._encode_call("name()", [], [])
        )
        symbol_data = await self._eth_call(
            vault_address, self._encode_call("symbol()", [], [])
        )
        asset_data = await self._eth_call(
            vault_address, self._encode_call("asset()", [], [])
        )
        trader_data = await self._eth_call(
            vault_address, self._encode_call("trader()", [], [])
        )
        owner_data = await self._eth_call(
            vault_address, self._encode_call("owner()", [], [])
        )
        total_assets_data = await self._eth_call(
            vault_address, self._encode_call("totalAssets()", [], [])
        )
        total_supply_data = await self._eth_call(
            vault_address, self._encode_call("totalSupply()", [], [])
        )
        share_price_data = await self._eth_call(
            vault_address, self._encode_call("sharePrice()", [], [])
        )
        hwm_data = await self._eth_call(
            vault_address, self._encode_call("highWaterMark()", [], [])
        )
        decimals_data = await self._eth_call(
            vault_address, self._encode_call("decimals()", [], [])
        )
        guardrails_data = await self._eth_call(
            vault_address, self._encode_call("guardrails()", [], [])
        )

        (name,) = self._decode_result(["string"], name_data)
        (symbol,) = self._decode_result(["string"], symbol_data)
        (asset,) = self._decode_result(["address"], asset_data)
        (trader,) = self._decode_result(["address"], trader_data)
        (owner,) = self._decode_result(["address"], owner_data)
        (total_assets,) = self._decode_result(["uint256"], total_assets_data)
        (total_supply,) = self._decode_result(["uint256"], total_supply_data)
        (share_price,) = self._decode_result(["uint256"], share_price_data)
        (hwm,) = self._decode_result(["uint256"], hwm_data)
        (decimals,) = self._decode_result(["uint8"], decimals_data)
        g_tuple = self._decode_result(["(uint16,uint16,uint16)"], guardrails_data)
        g = g_tuple[0]

        guardrails = Guardrails(
            max_leverage_bps=g[0],
            max_drawdown_bps=g[1],
            max_position_bps=g[2],
        )

        return Vault(
            address=vault_address,
            name=name,
            symbol=symbol,
            asset=asset,
            trader=trader,
            owner=owner,
            total_assets=total_assets,
            total_supply=total_supply,
            share_price=share_price,
            high_water_mark=hwm,
            guardrails=guardrails,
            decimals=decimals,
        )

    async def nav(self, vault_address: str) -> VaultNav:
        """Return a compact NAV snapshot: share price, total assets, total supply.

        Matches the TypeScript SDK's ``VaultNav`` type.
        """
        share_price_data = await self._eth_call(
            vault_address, self._encode_call("sharePrice()", [], [])
        )
        total_assets_data = await self._eth_call(
            vault_address, self._encode_call("totalAssets()", [], [])
        )
        total_supply_data = await self._eth_call(
            vault_address, self._encode_call("totalSupply()", [], [])
        )

        (share_price,) = self._decode_result(["uint256"], share_price_data)
        (total_assets,) = self._decode_result(["uint256"], total_assets_data)
        (total_supply,) = self._decode_result(["uint256"], total_supply_data)

        return VaultNav(
            address=vault_address,
            share_price=share_price,
            total_assets=total_assets,
            total_supply=total_supply,
        )

    # ------------------------------------------------------------------
    # Internals
    # ------------------------------------------------------------------

    @staticmethod
    def _parse_vault_deployed(receipt: dict) -> str:
        """Extract the vault address from the VaultDeployed event log."""
        # VaultDeployed(address indexed trader, address indexed vault, ...)
        # topic[0] = event sig, topic[1] = trader, topic[2] = vault
        for log in receipt.get("logs", []):
            topics = log.get("topics", [])
            if len(topics) >= 3:
                # The vault address is in topic[2], right-padded to 32 bytes
                raw = topics[2]
                if isinstance(raw, str) and len(raw) == 66:
                    return "0x" + raw[26:]
        raise TransactionError("Failed to parse VaultDeployed event")
