"""Strategy base class — NautilusTrader-inspired lifecycle hooks.

Subclass ``Strategy`` and implement handlers. The same code runs in
backtest and live mode with zero changes.
"""

from __future__ import annotations

import logging
import uuid
from typing import TYPE_CHECKING, Any, Dict, List, Optional

from ..events.bus import EventBus
from ..execution.enums import OrderSide, OrderType, TimeInForce
from ..execution.models import OrderSubmitRequest
from ..model.events import (
    BarEvent,
    FillEvent,
    OrderEvent,
    PositionEvent,
    QuoteEvent,
    SignalEvent,
    TimeEvent,
    TradeEvent,
)
from .config import StrategyConfig
from .context import Cache, Clock, Portfolio

if TYPE_CHECKING:
    from ..execution.router import BrokerRouter


class Strategy:
    """Base strategy class with NautilusTrader-style lifecycle hooks.

    Override any ``on_*`` method to implement your trading logic.
    Use ``buy()``, ``sell()``, ``submit_order()``, ``cancel_order()``,
    ``flatten()``, and ``flatten_all()`` for order management.

    Provides ``self.cache``, ``self.portfolio``, ``self.clock``,
    ``self.log``, and ``self.config`` for state access.
    """

    def __init__(self, config: Optional[StrategyConfig] = None) -> None:
        self.config = config or StrategyConfig()
        if not self.config.strategy_id:
            self.config.strategy_id = f"{type(self).__name__}-{uuid.uuid4().hex[:8]}"

        self.strategy_id: str = self.config.strategy_id
        self.log = logging.getLogger(f"cpz.strategy.{self.strategy_id}")

        # Injected by StrategyRunner before on_start
        self.cache: Cache = Cache()
        self.clock: Clock = Clock()
        self.portfolio: Portfolio = Portfolio()
        self._bus: Optional[EventBus] = None
        self._router: Optional["BrokerRouter"] = None
        self._subscriptions: List[Dict[str, str]] = []
        self._timers: Dict[str, Any] = {}
        self._running = False

    # ------------------------------------------------------------------
    # Lifecycle hooks (override these)
    # ------------------------------------------------------------------

    def on_start(self) -> None:
        """Called when the strategy starts. Subscribe to data here."""

    def on_stop(self) -> None:
        """Called when the strategy stops. Cleanup here."""

    def on_resume(self) -> None:
        """Called when the strategy resumes after a pause."""

    def on_reset(self) -> None:
        """Called when the strategy is reset."""

    # ------------------------------------------------------------------
    # Data handlers (override these)
    # ------------------------------------------------------------------

    def on_bar(self, bar: BarEvent) -> None:
        """Called when a new bar is received."""

    def on_quote(self, quote: QuoteEvent) -> None:
        """Called when a new quote is received."""

    def on_trade(self, trade: TradeEvent) -> None:
        """Called when a new trade tick is received."""

    def on_data(self, data: Any) -> None:
        """Called when custom data is received."""

    def on_signal(self, signal: SignalEvent) -> None:
        """Called when a signal event is received."""

    # ------------------------------------------------------------------
    # Order handlers (override these)
    # ------------------------------------------------------------------

    def on_order_submitted(self, event: OrderEvent) -> None:
        """Called when an order is submitted to the broker."""

    def on_order_accepted(self, event: OrderEvent) -> None:
        """Called when an order is accepted by the broker."""

    def on_order_rejected(self, event: OrderEvent) -> None:
        """Called when an order is rejected."""

    def on_order_filled(self, event: FillEvent) -> None:
        """Called when an order is fully filled."""

    def on_order_partially_filled(self, event: FillEvent) -> None:
        """Called when an order is partially filled."""

    def on_order_canceled(self, event: OrderEvent) -> None:
        """Called when an order is canceled."""

    def on_order_expired(self, event: OrderEvent) -> None:
        """Called when an order expires."""

    def on_order_event(self, event: OrderEvent) -> None:
        """Called for any order event (catch-all)."""

    # ------------------------------------------------------------------
    # Position handlers (override these)
    # ------------------------------------------------------------------

    def on_position_opened(self, event: PositionEvent) -> None:
        """Called when a new position is opened."""

    def on_position_changed(self, event: PositionEvent) -> None:
        """Called when a position size changes."""

    def on_position_closed(self, event: PositionEvent) -> None:
        """Called when a position is fully closed."""

    def on_position_event(self, event: PositionEvent) -> None:
        """Called for any position event (catch-all)."""

    # ------------------------------------------------------------------
    # Timer handlers (override these)
    # ------------------------------------------------------------------

    def on_timer(self, event: TimeEvent) -> None:
        """Called when a timer fires."""

    # ------------------------------------------------------------------
    # Subscription management
    # ------------------------------------------------------------------

    def subscribe_bars(self, symbol: str, timeframe: str = "1D") -> None:
        """Subscribe to bar data for *symbol* at *timeframe*."""
        sub = {"type": "bar", "symbol": symbol, "timeframe": timeframe}
        if sub not in self._subscriptions:
            self._subscriptions.append(sub)
        if self._bus:
            topic = f"bar.{symbol}.{timeframe}"
            self._bus.subscribe(topic, self._handle_bar)

    def subscribe_quotes(self, symbol: str) -> None:
        sub = {"type": "quote", "symbol": symbol}
        if sub not in self._subscriptions:
            self._subscriptions.append(sub)
        if self._bus:
            self._bus.subscribe(f"quote.{symbol}", self._handle_quote)

    def subscribe_trades(self, symbol: str) -> None:
        sub = {"type": "trade", "symbol": symbol}
        if sub not in self._subscriptions:
            self._subscriptions.append(sub)
        if self._bus:
            self._bus.subscribe(f"trade.{symbol}", self._handle_trade)

    # ------------------------------------------------------------------
    # Order management
    # ------------------------------------------------------------------

    def buy(
        self,
        symbol: str,
        qty: float | None = None,
        notional: float | None = None,
        order_type: str = "market",
        time_in_force: str = "DAY",
        limit_price: float | None = None,
    ) -> Any:
        """Submit a BUY order."""
        return self._place_order(
            symbol=symbol,
            side="buy",
            qty=qty,
            notional=notional,
            order_type=order_type,
            time_in_force=time_in_force,
            limit_price=limit_price,
        )

    def sell(
        self,
        symbol: str,
        qty: float | None = None,
        notional: float | None = None,
        order_type: str = "market",
        time_in_force: str = "DAY",
        limit_price: float | None = None,
    ) -> Any:
        """Submit a SELL order."""
        return self._place_order(
            symbol=symbol,
            side="sell",
            qty=qty,
            notional=notional,
            order_type=order_type,
            time_in_force=time_in_force,
            limit_price=limit_price,
        )

    def submit_order(self, req: OrderSubmitRequest) -> Any:
        """Submit an arbitrary order request."""
        return self._execute_order(req)

    def cancel_order(self, order_id: str) -> Any:
        if self._router:
            return self._router.cancel_order(order_id)

    def cancel_all_orders(self) -> None:
        if self._router:
            try:
                orders = self._router.list_orders(status="open")
                for o in orders:
                    if hasattr(o, "id"):
                        self._router.cancel_order(o.id)
            except Exception as exc:
                self.log.warning("cancel_all_orders failed: %s", exc)

    def flatten(self, symbol: str) -> Any:
        """Close position for *symbol* with a market order."""
        pos = self.portfolio.position(symbol)
        if pos is None or pos.is_flat:
            return None
        side = "sell" if pos.qty > 0 else "buy"
        return self._place_order(symbol=symbol, side=side, qty=abs(pos.qty))

    def flatten_all(self) -> None:
        """Close all open positions."""
        for sym, pos in list(self.portfolio.positions().items()):
            self.flatten(sym)

    def emit_signal(self, name: str, value: float = 0.0, data: dict | None = None) -> None:
        """Emit a custom signal onto the event bus."""
        sig = SignalEvent(name=name, value=value, data=data or {}, source=self.strategy_id)
        if self._bus:
            self._bus.publish(f"signal.{name}", sig)

    # ------------------------------------------------------------------
    # Internal dispatch (called by StrategyRunner / BacktestEngine)
    # ------------------------------------------------------------------

    def _handle_bar(self, bar: BarEvent) -> None:
        self.cache.push_bar(bar)
        self.portfolio.update_price(bar.symbol, bar.close)
        self.on_bar(bar)

    def _handle_quote(self, quote: QuoteEvent) -> None:
        self.cache.set_quote(quote.symbol, quote)
        self.on_quote(quote)

    def _handle_trade(self, trade: TradeEvent) -> None:
        self.on_trade(trade)

    def _handle_fill(self, fill: FillEvent) -> None:
        was_flat = self.portfolio.is_flat(fill.symbol)
        self.portfolio.apply_fill(fill)
        now_flat = self.portfolio.is_flat(fill.symbol)

        self.on_order_filled(fill)

        if was_flat and not now_flat:
            self._emit_position_event(fill.symbol, "opened")
        elif not was_flat and now_flat:
            self._emit_position_event(fill.symbol, "closed")
        elif not was_flat and not now_flat:
            self._emit_position_event(fill.symbol, "changed")

    def _emit_position_event(self, symbol: str, action: str) -> None:
        pos = self.portfolio.position(symbol)
        evt = PositionEvent(
            symbol=symbol,
            qty=pos.qty if pos else 0.0,
            avg_entry_price=pos.avg_entry_price if pos else 0.0,
            realized_pnl=pos.realized_pnl if pos else 0.0,
            action=action,
            strategy_id=self.strategy_id,
        )
        if action == "opened":
            self.on_position_opened(evt)
        elif action == "changed":
            self.on_position_changed(evt)
        elif action == "closed":
            self.on_position_closed(evt)
        self.on_position_event(evt)

    def _place_order(
        self,
        symbol: str,
        side: str,
        qty: float | None = None,
        notional: float | None = None,
        order_type: str = "market",
        time_in_force: str = "DAY",
        limit_price: float | None = None,
    ) -> Any:
        side_enum = OrderSide.BUY if side.lower() == "buy" else OrderSide.SELL
        type_map = {
            "market": OrderType.MARKET,
            "limit": OrderType.LIMIT,
            "stop": OrderType.STOP,
            "stop_limit": OrderType.STOP_LIMIT,
            "trailing_stop": OrderType.TRAILING_STOP,
        }
        type_enum = type_map.get(order_type.lower(), OrderType.MARKET)
        tif_enum = TimeInForce(time_in_force.upper())

        req = OrderSubmitRequest(
            symbol=symbol,
            side=side_enum,
            qty=float(qty) if qty is not None else None,
            notional=float(notional) if notional is not None else None,
            order_type=type_enum,
            time_in_force=tif_enum,
            limit_price=limit_price,
            strategy_id=self.strategy_id,
        )
        return self._execute_order(req)

    def _execute_order(self, req: OrderSubmitRequest) -> Any:
        """Route order to the runner's execution layer (live or backtest)."""
        if self._router:
            order = self._router.submit_order(req)
            submitted_evt = OrderEvent(
                order_id=order.id,
                symbol=req.symbol,
                side=req.side.value,
                qty=req.qty or 0.0,
                order_type=req.type.value,
                status="submitted",
                strategy_id=self.strategy_id,
            )
            self.on_order_submitted(submitted_evt)
            self.on_order_event(submitted_evt)
            return order

        if self._bus:
            self._bus.publish("order.submit", req)
        return None
