"""``ctx.data`` — the data-feed provider seam (plan A1.4).

Future external feeds (weather, injuries, odds, ...) must be **point-in-time** in a backtest: a
strategy asking for a datum during ``on_event`` may only see what was knowable ``as_of`` that
event's timestamp, never anything later. This module lands the SEAM only — the ``DataFeedProvider``
Protocol every provider (null, recorded, and eventually live) must satisfy, plus the honest default
that says "no feeds here" rather than fabricating a value. Concrete recorded feeds are a dedicated
PIT-ingestion sub-project; see :class:`ante.datafeeds.RecordedDataFeedProvider` (engine side) for
the stub that will grow them.
"""

from __future__ import annotations

from datetime import datetime
from typing import Protocol, runtime_checkable


class FeedUnavailable(Exception):
    """Raised by a :class:`DataFeedProvider` when ``feed``/``key`` cannot be resolved as of
    ``as_of`` — no such feed is wired for this transport, or point-in-time data for it doesn't
    exist (yet). A strategy that declares an optional data dependency should catch this and
    degrade gracefully rather than crash; the provider must raise rather than return ``None``, so
    "unavailable" is never confused with a real, falsy datum.
    """


@runtime_checkable
class DataFeedProvider(Protocol):
    """The ``ctx.data`` surface a strategy's ``on_event`` sees.

    ``fetch`` must be a pure function of ``(feed, key, as_of)`` — the SAME triple returns the SAME
    value forever (or raises), so a backtest replay stays byte-reproducible. No implementation may
    answer from information later than ``as_of``; that is what makes a backtest against it honest.
    """

    def fetch(self, feed: str, key: str, as_of: datetime) -> object:
        """Return the datum for ``feed``/``key`` as it was known at ``as_of``.

        Raises :class:`FeedUnavailable` if the feed/key/timeframe cannot be resolved.
        """
        ...


class NullDataFeedProvider:
    """Default ``ctx.data``: this transport carries zero external feeds.

    Every ``fetch`` is a hard, honest refusal — never a silent ``None`` — so a strategy that
    expects a feed to be wired is told plainly rather than fed a value that happens to look
    lookahead-free but is simply absent.
    """

    def fetch(self, feed: str, key: str, as_of: datetime) -> object:
        raise FeedUnavailable("no data feeds in this transport")
