"""
Funding Rate Arbitrage strategy.

Hyperliquid pays funding every 8 hours. When the rate is positive,
longs pay shorts. This strategy:

1. Goes LONG spot (no funding payments)
2. Goes SHORT perp (collects funding)
3. Maintains delta neutrality

The profit comes from funding, not price direction. The strategy
fetches real funding rates from Hyperliquid's API every bar
and enters/exits based on the rate crossing configurable thresholds.
"""
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.config import StrategyConfig

from common.hyperliquid_api import get_funding_rate, get_predicted_funding


class FundingRateArbConfig(StrategyConfig, frozen=True):
    spot_instrument: str
    perp_instrument: str
    min_funding_rate: float = 0.0001      # 0.01% annualized ~ 10.95% APR
    rebalance_threshold: float = 0.05     # 5% PnL deviation triggers rebalance
    position_size: float = 0.01           # BTC
    use_predicted: bool = True            # Use predicted funding rate
    testnet: bool = True


class FundingRateArb(Strategy):
    """
    Delta-neutral funding rate carry trade.

    Key concept: the funding rate IS the edge.
    Direction doesn't matter — neutrality does.

    Entry: when funding rate > min_funding_rate AND no position
    Exit:  when funding rate drops below half the entry threshold
    """

    def __init__(self, config: FundingRateArbConfig) -> None:
        super().__init__(config)
        self.config = config
        self.position_open = False
        self.bars_elapsed = 0

    def on_start(self) -> None:
        bar_type = f"{self.config.perp_instrument}-1-MINUTE-LAST-INTERNAL"
        self.subscribe_bars(bar_type)
        self.log.info(
            f"Funding arb started: "
            f"{self.config.spot_instrument} / {self.config.perp_instrument} "
            f"(min_rate={self.config.min_funding_rate:.4%}, "
            f"size={self.config.position_size})"
        )

    def on_bar(self, bar) -> None:
        # Check funding every 5 bars to avoid hammering the API
        self.bars_elapsed += 1
        if self.bars_elapsed % 5 != 0:
            return

        # Fetch real funding rate from Hyperliquid
        asset = self._extract_asset(self.config.perp_instrument)
        if self.config.use_predicted:
            funding_rate = get_predicted_funding(asset, testnet=self.config.testnet)
        else:
            funding_rate = get_funding_rate(asset, testnet=self.config.testnet)

        if funding_rate is None:
            return  # API call failed, skip this bar

        spot_pos = float(self.portfolio.net_position(self.config.spot_instrument))

        # Entry condition: funding rate is attractive and we have no position
        if funding_rate > self.config.min_funding_rate and spot_pos == 0:
            self.log.info(
                f"Entering funding arb: rate={funding_rate:.6f} "
                f"(>{self.config.min_funding_rate:.6f})"
            )
            self._open_arb()
            self.position_open = True

        # Exit condition: funding rate no longer worth the risk
        elif funding_rate < self.config.min_funding_rate / 2 and self.position_open:
            self.log.info(
                f"Closing funding arb: rate={funding_rate:.6f} "
                f"(<{self.config.min_funding_rate / 2:.6f})"
            )
            self._close_arb()
            self.position_open = False

    def _extract_asset(self, instrument: str) -> str:
        """Extract asset name from instrument ID (e.g. BTC-USD-PERP -> BTC)."""
        return instrument.split("-")[0]

    def _open_arb(self) -> None:
        """Long spot, short perp — delta neutral."""
        self.submit_order(self.order_factory.market(
            instrument_id=self.config.spot_instrument,
            order_side="BUY",
            quantity=self.config.position_size,
        ))
        self.submit_order(self.order_factory.market(
            instrument_id=self.config.perp_instrument,
            order_side="SELL",
            quantity=self.config.position_size,
        ))

    def _close_arb(self) -> None:
        """Close both legs."""
        self.close_all_positions(self.config.spot_instrument)
        self.close_all_positions(self.config.perp_instrument)
