"""
Base strategy class for NautilusTrader + Hyperliquid.

Provides shared lifecycle for all FTDT strategies:
  - Instrument resolution from Hyperliquid catalog
  - Fee-aware position sizing from StrategyConfig
  - Shared signal pipeline (OBI, Hurst, VPIN computations)
  - on_start / on_bar / on_stop hooks

Strategies inherit this and override signal logic.
"""
from __future__ import annotations

import logging
from collections import deque
from typing import Any

import numpy as np

from nautilus_trader.common.actor import Actor
from nautilus_trader.model.data import Bar, BarSpecification, BarType
from nautilus_trader.model.enums import BarAggregation, OrderSide, PriceType
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.objects import Price, Quantity
from nautilus_trader.trading.strategy import Strategy

from framework.config import StrategyConfig
from framework.data import HyperliquidDataProvider
from framework.instruments import HyperliquidInstrumentCatalog

logger = logging.getLogger(__name__)


class BaseHlStrategy(Strategy):
    """Base strategy with Hyperliquid-specific utilities.

    Inherits NautilusTrader Strategy lifecycle:
      on_start → on_bar (repeated) → on_stop
    """

    def __init__(self, config: StrategyConfig):
        super().__init__()
        self._cfg = config
        self._instrument: InstrumentId | None = None
        self._asset = config.asset

        # Price history for signal calculations
        self._prices: deque[float] = deque(maxlen=300)

        # Signal state
        self._last_signal: dict[str, Any] | None = None
        self._position_open: bool = False
        self._entry_price: float = 0.0

    @property
    def config(self) -> StrategyConfig:
        return self._cfg

    @property
    def instrument_id(self) -> InstrumentId | None:
        return self._instrument

    # ── Lifecycle ───────────────────────────────────────────────

    def on_start(self):
        """Called when strategy is started. Resolve instruments."""
        if not self._instrument:
            # Try to resolve from catalog
            catalog = HyperliquidInstrumentCatalog(testnet=self._cfg.testnet)
            inst_map = catalog.load(assets=[self._asset])
            inst = inst_map.get(self._asset.upper())
            if inst:
                self._instrument = inst.id
            else:
                self._instrument = InstrumentId.from_str(
                    f"{self._asset.upper()}-USD-PERP.HYPERLIQUID"
                )

        # Subscribe to 1-minute bars
        bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST)
        bar_type = BarType(self._instrument, bar_spec)
        self.subscribe_bars(bar_type)
        logger.info("%s started on %s", self._cfg.name, self._instrument)

    def on_stop(self):
        logger.info("%s stopped", self._cfg.name)

    def on_bar(self, bar: Bar):
        """Process each bar. Override in subclasses for custom signal logic."""
        self._prices.append(float(bar.close))
        signal = self.compute_signal()
        if signal:
            self._last_signal = signal
            self.handle_signal(signal)

    # ── Signal computation (override in subclass) ───────────────

    def compute_signal(self) -> dict[str, Any] | None:
        """Override in subclass to compute trading signals."""
        return None

    def handle_signal(self, signal: dict[str, Any]):
        """Default: submit a limit order based on signal direction."""
        side = signal.get("signal", "")
        strength = signal.get("strength", 0.0)

        # Check minimum strength threshold
        if strength < 0.15:
            return

        if "BUY" in str(side).upper():
            self._submit_order(OrderSide.BUY)
        elif "SELL" in str(side).upper():
            self._submit_order(OrderSide.SELL)

    # ── Order submission ──────────────────────────────────────

    def _submit_order(self, side, size: float | None = None):
        """Submit a limit order.

        In backtest mode: NT engine handles fill emulation via bars.
        In live mode: order goes through the execution provider.

        Override in subclass for venue-specific order construction.
        """
        sz = size or self._cfg.order_size
        price = self._prices[-1] if self._prices else 0.0
        if price <= 0 or sz <= 0:
            return

        try:
            from nautilus_trader.model.objects import Price, Quantity

            self.submit_order(
                instrument_id=self._instrument,
                order_side=side,
                order_type="LIMIT",
                quantity=Quantity.from_str(str(sz)),
                price=Price.from_str(str(int(price))),
                post_only=True,
            )
        except (TypeError, ValueError, AttributeError):
            logger.debug("%s: order not submitted (venue-specific API needed)", self._cfg.name)

    # ── Signal library (shared across strategies) ───────────────

    def signal_zscore(self, window: int = 20, threshold: float = 1.5) -> dict | None:
        """Z-score mean reversion signal based on price history."""
        if len(self._prices) < window:
            return None
        prices = list(self._prices)
        recent = prices[-window:]
        mu = np.mean(recent)
        std = np.std(recent, ddof=1)
        if std <= 0:
            return None
        z = (prices[-1] - mu) / std
        if z > threshold:
            return {"signal": "SELL", "strength": z / threshold}
        elif z < -threshold:
            return {"signal": "BUY", "strength": abs(z) / threshold}
        return None

    def signal_bollinger(self, window: int = 20, n_std: float = 2.0) -> dict | None:
        """Bollinger band breakout signal."""
        if len(self._prices) < window:
            return None
        prices = list(self._prices)
        recent = prices[-window:]
        sma = np.mean(recent)
        std = np.std(recent, ddof=1)
        if std <= 0:
            return None
        cur = prices[-1]
        if cur > sma + n_std * std:
            return {"signal": "BUY", "strength": (cur - sma - n_std * std) / std}
        elif cur < sma - n_std * std:
            return {"signal": "SELL", "strength": (sma - n_std * std - cur) / std}
        return None

    def signal_trend(self, window: int = 10, threshold: float = 0.7) -> dict | None:
        """Directional trend strength signal."""
        if len(self._prices) < window:
            return None
        prices = list(self._prices)
        up = sum(1 for i in range(-window + 1, 0) if prices[i + 1] > prices[i])
        ratio = up / (window - 1)
        if ratio >= threshold:
            return {"signal": "BUY", "strength": ratio}
        elif ratio <= 1.0 - threshold:
            return {"signal": "SELL", "strength": 1.0 - ratio}
        return None

    def signal_vwap_deviation(self, window: int = 20, threshold: float = 1.0) -> dict | None:
        """VWAP deviation signal (mean-reverting)."""
        if len(self._prices) < window:
            return None
        prices = list(self._prices)
        prior = prices[-(window + 1):-1]
        cur = prices[-1]
        vwap = np.mean(prior)
        std = np.std(prior, ddof=1)
        if std <= 0:
            return None
        dev = (cur - vwap) / std
        if dev > threshold:
            return {"signal": "SELL", "strength": dev / threshold}
        elif dev < -threshold:
            return {"signal": "BUY", "strength": abs(dev) / threshold}
        return None
