"""PortfolioModule — read vault portfolio state from on-chain data."""

from __future__ import annotations

from typing import List

from eth_abi.exceptions import DecodingError

from openhedge.constants import PAIR_INDEX
from openhedge.exceptions import NetworkError, TransactionError
from openhedge.models.portfolio import Allocation, Portfolio
from openhedge.models.trade import Position
from openhedge.modules.base import BaseModule


# Reverse map: pairIndex -> pair name
_INDEX_TO_PAIR = {v: k for k, v in PAIR_INDEX.items()}


class PortfolioModule(BaseModule):
    """Aggregate on-chain vault state into a portfolio view."""

    async def get(self, vault_address: str) -> Portfolio:
        """Build a portfolio snapshot for *vault_address*.

        Reads total assets, share price, total supply, and enumerates
        open perpetual positions via the Gains adapter.
        """
        total_assets_data = await self._eth_call(
            vault_address, self._encode_call("totalAssets()", [], [])
        )
        share_price_data = await self._eth_call(
            vault_address, self._encode_call("sharePrice()", [], [])
        )
        total_supply_data = await self._eth_call(
            vault_address, self._encode_call("totalSupply()", [], [])
        )

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

        # Count open perpetual positions via GainsAdapter
        open_count = 0
        gains_adapter = self._contracts["adapters"]["gains"]
        if gains_adapter != "0x" + "0" * 40:
            try:
                indices_data = await self._eth_call(
                    gains_adapter,
                    self._encode_call(
                        "getOpenTradeIndices(address)", ["address"], [vault_address]
                    ),
                )
                (indices,) = self._decode_result(["uint256[]"], indices_data)
                open_count = len(indices)
            except (NetworkError, TransactionError, DecodingError):
                pass  # Adapter may not be deployed or accessible

        # Build basic allocations
        allocations: List[Allocation] = []
        if total_assets > 0:
            allocations.append(
                Allocation(
                    asset=vault_address,
                    protocol="spot",
                    amount=total_assets,
                    percentage_bps=10000,
                )
            )

        return Portfolio(
            vault=vault_address,
            total_assets=total_assets,
            share_price=share_price,
            total_supply=total_supply,
            allocations=allocations,
            open_positions_count=open_count,
        )

    async def positions(self, vault_address: str) -> List[Position]:
        """List all open perpetual positions for the vault.

        Reads the GainsAdapter to enumerate and hydrate each position.
        """
        gains_adapter = self._contracts["adapters"]["gains"]
        if gains_adapter == "0x" + "0" * 40:
            return []

        # getOpenTradeIndices(address vault) -> uint256[]
        indices_data = await self._eth_call(
            gains_adapter,
            self._encode_call(
                "getOpenTradeIndices(address)", ["address"], [vault_address]
            ),
        )
        (indices,) = self._decode_result(["uint256[]"], indices_data)

        positions: List[Position] = []
        for idx in indices:
            # getTradeInfo(uint256 tradeIndex)
            # returns (address trader, uint256 pairIndex, bool isLong,
            #          uint256 collateral, uint256 leverage, uint256 tp,
            #          uint256 sl, bool open)
            info_data = await self._eth_call(
                gains_adapter,
                self._encode_call(
                    "getTradeInfo(uint256)", ["uint256"], [idx]
                ),
            )
            (trader, pair_index, is_long, collateral, leverage, tp, sl, is_open) = (
                self._decode_result(
                    [
                        "address",
                        "uint256",
                        "bool",
                        "uint256",
                        "uint256",
                        "uint256",
                        "uint256",
                        "bool",
                    ],
                    info_data,
                )
            )

            positions.append(
                Position(
                    trade_index=idx,
                    vault=vault_address,
                    pair_index=pair_index,
                    pair_name=_INDEX_TO_PAIR.get(pair_index),
                    is_long=is_long,
                    collateral=collateral,
                    leverage=leverage,
                    take_profit=tp,
                    stop_loss=sl,
                    is_open=is_open,
                )
            )

        return positions
