defmodule Tai.Markets.Quote do
  @moduledoc """
  Represents the inside bid & ask price point within the order book
  """

  alias __MODULE__
  alias Tai.Markets.PricePoint

  @type price_point :: PricePoint.t()
  @type venue_id :: Tai.Venue.id()
  @type product_symbol :: Tai.Venues.Product.symbol()
  @type t :: %Quote{
          venue_id: venue_id,
          product_symbol: product_symbol,
          bids: [price_point],
          asks: [price_point],
          last_received_at: integer,
          last_venue_timestamp: DateTime.t() | nil
        }

  @enforce_keys ~w[venue_id product_symbol bids asks last_received_at]a
  defstruct venue_id: nil,
            product_symbol: nil,
            bids: [],
            asks: [],
            last_received_at: nil,
            last_venue_timestamp: nil

  @spec inside_bid(t) :: price_point | nil
  def inside_bid(market_quote), do: market_quote.bids |> List.first()

  @spec inside_ask(t) :: price_point | nil
  def inside_ask(market_quote), do: market_quote.asks |> List.first()

  @spec mid_price(t) :: {:ok, Decimal.t()} | {:error, :no_inside_bid | :no_inside_ask}
  @spec mid_price(bid :: price_point, ask :: price_point) ::
          {:ok, Decimal.t()} | {:error, :no_inside_bid | :no_inside_ask | :no_inside_bid_or_ask}
  def mid_price(%Quote{} = market_quote) do
    bid = market_quote |> inside_bid
    ask = market_quote |> inside_ask
    mid_price(bid, ask)
  end

  @two Decimal.new(2)
  def mid_price(%PricePoint{} = bid, %PricePoint{} = ask) do
    ask_price = Tai.Utils.Decimal.cast!(ask.price)
    bid_price = Tai.Utils.Decimal.cast!(bid.price)

    mid =
      ask_price
      |> Decimal.sub(bid_price)
      |> Decimal.div(@two)
      |> Decimal.add(bid_price)

    {:ok, mid}
  end

  def mid_price(nil, %PricePoint{}), do: {:error, :no_inside_bid}
  def mid_price(%PricePoint{}, nil), do: {:error, :no_inside_ask}
  def mid_price(nil, nil), do: {:error, :no_inside_bid_or_ask}

  defimpl Stored.Item do
    @type market_quote :: Tai.Markets.Quote.t()
    @type venue_id :: Tai.Venue.id()
    @type product_symbol :: Tai.Venues.Product.symbol()

    @spec key(market_quote) :: {venue_id, product_symbol}
    def key(q), do: {q.venue_id, q.product_symbol}
  end
end
