"""Order Book Imbalance (OBI) strategy.

Fires on OrderBookEvent. Computes volume imbalance across ``depth`` price levels.

    imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol)

- Enter BUY_YES when imbalance > entry_threshold and no position
- Exit SELL when imbalance < exit_threshold OR elapsed > max_hold_seconds
"""

from __future__ import annotations

import logging
from collections import deque
from datetime import datetime
from decimal import Decimal

from coinjure.events.events import Event, OrderBookEvent
from coinjure.ticker.ticker import Ticker
from coinjure.trader.trader import Trader
from coinjure.trader.types import TradeSide

from .strategy import Strategy, StrategyDecision


class OrderBookImbalanceStrategy(Strategy):
    def __init__(
        self,
        tickers: list[Ticker] | None = None,
        depth: int = 3,
        entry_threshold: float = 0.3,
        exit_threshold: float = -0.1,
        position_size: Decimal = Decimal('10'),
        max_hold_seconds: int = 300,
    ) -> None:
        ticker_list = tickers or []
        self.tickers: set[str] = {t.symbol for t in ticker_list}
        self._ticker_map: dict[str, Ticker] = {t.symbol: t for t in ticker_list}
        self.depth = depth
        self.entry_threshold = entry_threshold
        self.exit_threshold = exit_threshold
        self.position_size = position_size
        self.max_hold_seconds = max_hold_seconds
        self.logger = logging.getLogger(__name__)

        self.decisions: deque[StrategyDecision] = deque(maxlen=200)
        self.total_decisions: int = 0
        self.total_executed: int = 0
        self.total_buy_yes: int = 0
        self.total_holds: int = 0
        self.total_closes: int = 0

        # symbol → entry_time
        self._entries: dict[str, datetime] = {}
        self._closing_in_progress: set[str] = set()

    async def process_event(self, event: Event, trader: Trader) -> None:
        if self.is_paused():
            return
        if not isinstance(event, OrderBookEvent):
            return
        ticker = event.ticker
        if self.tickers and ticker.symbol not in self.tickers:
            return

        ob = trader.market_data.order_books.get(ticker)
        if ob is None:
            return

        bids = ob.get_bids(self.depth)
        asks = ob.get_asks(self.depth)
        bid_vol = float(sum(lv.size for lv in bids))
        ask_vol = float(sum(lv.size for lv in asks))
        total_vol = bid_vol + ask_vol
        if total_vol == 0:
            return

        imbalance = (bid_vol - ask_vol) / total_vol

        position = trader.position_manager.get_position(ticker)
        has_position = position is not None and position.quantity > 0

        now = datetime.now()

        if has_position:
            # Check exit conditions
            if ticker.symbol in self._closing_in_progress:
                return
            entry_time = self._entries.get(ticker.symbol)
            elapsed = (now - entry_time).total_seconds() if entry_time else 0

            should_exit = (
                imbalance < self.exit_threshold or elapsed > self.max_hold_seconds
            )
            if should_exit:
                action = (
                    'CLOSE_TIMEOUT' if elapsed > self.max_hold_seconds else 'CLOSE_OBI'
                )
                self._closing_in_progress.add(ticker.symbol)
                try:
                    bid = trader.market_data.get_best_bid(ticker)
                    if bid is None:
                        return
                    result = await trader.place_order(
                        side=TradeSide.SELL,
                        ticker=ticker,
                        limit_price=bid.price,
                        quantity=position.quantity,
                    )
                    executed = result.order is not None
                    if executed:
                        self._entries.pop(ticker.symbol, None)
                        self.total_executed += 1
                    self._record_decision(
                        ticker_name=ticker.name or ticker.symbol,
                        action=action,
                        executed=executed,
                        reasoning=f'imbalance={imbalance:.3f}, elapsed={elapsed:.0f}s',
                        signal_values={
                            'imbalance': imbalance,
                            'bid_vol': bid_vol,
                            'ask_vol': ask_vol,
                        },
                    )
                    self.total_closes += 1
                finally:
                    self._closing_in_progress.discard(ticker.symbol)
        else:
            # Check entry condition
            if imbalance > self.entry_threshold:
                ask = trader.market_data.get_best_ask(ticker)
                if ask is None:
                    return
                result = await trader.place_order(
                    side=TradeSide.BUY,
                    ticker=ticker,
                    limit_price=ask.price,
                    quantity=self.position_size,
                )
                executed = result.order is not None
                if executed:
                    self._entries[ticker.symbol] = now
                    self.total_executed += 1
                    self.total_buy_yes += 1
                self._record_decision(
                    ticker_name=ticker.name or ticker.symbol,
                    action='BUY_YES',
                    executed=executed,
                    reasoning=f'imbalance={imbalance:.3f} > threshold={self.entry_threshold}',
                    signal_values={
                        'imbalance': imbalance,
                        'bid_vol': bid_vol,
                        'ask_vol': ask_vol,
                    },
                )
            else:
                self.total_holds += 1
                self._record_decision(
                    ticker_name=ticker.name or ticker.symbol,
                    action='HOLD',
                    executed=False,
                    reasoning=f'imbalance={imbalance:.3f}',
                    signal_values={
                        'imbalance': imbalance,
                        'bid_vol': bid_vol,
                        'ask_vol': ask_vol,
                    },
                )

    def _record_decision(
        self,
        ticker_name: str,
        action: str,
        executed: bool,
        reasoning: str,
        signal_values: dict[str, float],
    ) -> None:
        self.decisions.append(
            StrategyDecision(
                timestamp=datetime.now().strftime('%H:%M:%S'),
                ticker_name=ticker_name[:40],
                action=action,
                executed=executed,
                reasoning=reasoning,
                signal_values=signal_values,
            )
        )
        self.total_decisions += 1

    def get_decisions(self) -> list[StrategyDecision]:
        return list(self.decisions)

    def get_decision_stats(self) -> dict[str, int | float]:
        return {
            'decisions': self.total_decisions,
            'executed': self.total_executed,
            'buy_yes': self.total_buy_yes,
            'holds': self.total_holds,
            'closes': self.total_closes,
        }
