from typing import Generator, NamedTuple
import json
from datetime import datetime

from v20.pricing import ClientPrice
from v20.pricing_common import PriceBucket as OandaPriceBucket
from algo.constant import Symbol

from algo.util import str_to_dt

JsonReadyPricePoint = tuple[str, float, float]


class PricePoint(NamedTuple):
    time: datetime
    ask: float
    bid: float

    def to_json(self) -> str:
        return json.dumps([self.time.isoformat(), self.ask, self.bid])

    def to_json_ready(self) -> JsonReadyPricePoint:
        return (self.time.isoformat(), self.ask, self.bid)

    @classmethod
    def from_json_ready(cls, data: JsonReadyPricePoint):
        return cls(*(datetime.fromisoformat(data[0]), data[1], data[2]))

    @staticmethod
    def is_valid_price_data(data: ClientPrice) -> bool:
        """ sometimes oanda just throws the type in the response ..."""
        return (data.time and data.closeoutAsk and data.closeoutBid) is not None

    @classmethod
    def from_oanda_price(cls, data: ClientPrice):
        return cls(str_to_dt(data.time), data.closeoutAsk, data.closeoutBid)


class PriceBucket(NamedTuple):
    """ naming is following oanda's don't have better ideas"""
    price: float
    liquidity: int

    @classmethod
    def from_oanda_price_bucket(cls, price_bucket: OandaPriceBucket):
        return cls(price_bucket.price, price_bucket.liquidity)

    @property
    def dict(self):
        return dict(price=self.price, liquidity=self.liquidity)


class CompletePricePoint(NamedTuple):
    time: datetime
    instrument: Symbol
    ask: float
    bid: float
    asks: list[PriceBucket]
    bids: list[PriceBucket]

    @staticmethod
    def is_valid_price_data(data: ClientPrice) -> bool:
        """ sometimes oanda just throws the type in the response ..."""
        return (data.time and data.closeoutAsk and data.closeoutBid) is not None

    @classmethod
    def from_oanda_price(cls, data: ClientPrice):
        return cls(
            str_to_dt(data.time),
            Symbol(data.instrument),
            data.closeoutAsk,
            data.closeoutBid,
            [PriceBucket.from_oanda_price_bucket(pb) for pb in data.asks],
            [PriceBucket.from_oanda_price_bucket(pb) for pb in data.bids]
        )

    @property
    def dict(self):
        return dict(
            time=self.time.isoformat(),
            instrument=self.instrument.value,
            ask=self.ask,
            bid=self.bid,
            asks=[d.dict for d in self.asks],
            bids=[d.dict for d in self.bids]
        )


PriceStream = Generator[PricePoint, None, None]
CompletePriceStream = Generator[CompletePricePoint, None, None]
