"""Context object injected into pipeline functions."""

from __future__ import annotations

import time as _time
from dataclasses import dataclass, field
from typing import Any

from horizon._horizon import EngineStatus, Event, Market, Position, Side


@dataclass
class FeedData:
    """Snapshot of a single feed."""

    price: float = 0.0
    timestamp: float = 0.0
    bid: float = 0.0
    ask: float = 0.0
    volume_24h: float = 0.0
    source: str = ""
    last_trade_size: float = 0.0
    last_trade_is_buy: bool = False
    _reference_time: float = field(default=0.0, repr=False, compare=False)

    def is_stale(self, max_age_secs: float = 30.0, now: float | None = None) -> bool:
        """Return True if the feed data is stale or has never been updated.

        Args:
            max_age_secs: Maximum age in seconds before data is considered stale.
            now: Override for current time (e.g. simulation clock in backtests).
                 Falls back to ``_reference_time`` if set, else ``time.time()``.
        """
        if self.timestamp <= 0:
            return True
        if now is None:
            now = self._reference_time if self._reference_time > 0 else _time.time()
        return (now - self.timestamp) > max_age_secs


@dataclass
class InventorySnapshot:
    """Current inventory state."""

    positions: list[Position] = field(default_factory=list)

    @property
    def net(self) -> float:
        """Net position across all markets (YES - NO)."""
        total = 0.0
        for pos in self.positions:
            if pos.side == Side.Yes:
                total += pos.size
            else:
                total -= pos.size
        return total

    def net_for_market(self, market_id: str) -> float:
        """Net position for a specific market (YES - NO)."""
        total = 0.0
        for pos in self.positions:
            if pos.market_id == market_id:
                if pos.side == Side.Yes:
                    total += pos.size
                else:
                    total -= pos.size
        return total

    def net_for_event(self, market_ids: list[str]) -> float:
        """Net position across all markets in an event."""
        total = 0.0
        ids = set(market_ids)
        for pos in self.positions:
            if pos.market_id in ids:
                if pos.side == Side.Yes:
                    total += pos.size
                else:
                    total -= pos.size
        return total

    def positions_for_event(self, market_ids: list[str]) -> list[Position]:
        """Filter positions for markets in an event."""
        ids = set(market_ids)
        return [p for p in self.positions if p.market_id in ids]


@dataclass
class Context:
    """Context injected into every pipeline function.

    Provides access to feeds, inventory, market info, engine status,
    and arbitrary user parameters.
    """

    feeds: dict[str, FeedData] = field(default_factory=dict)
    inventory: InventorySnapshot = field(default_factory=InventorySnapshot)
    market: Market | None = None
    event: Event | None = None
    status: EngineStatus | None = None
    params: dict[str, Any] = field(default_factory=dict)

    @property
    def feed(self) -> FeedData:
        """Convenience property: returns the first available feed snapshot.

        Checks "default" first, then falls back to the first feed in the dict.
        Returns an empty FeedData if no feeds are available.
        """
        if "default" in self.feeds:
            return self.feeds["default"]
        if self.feeds:
            return next(iter(self.feeds.values()))
        return FeedData()

    @property
    def market_id(self) -> str:
        """Convenience property: returns the current market's ID."""
        if self.market is not None:
            return self.market.id
        return ""
