defmodule Tai.VenueAdapters.OkEx.Stream.UpdateOrder do

  @date_format "{ISO:Extended:Z}"

  def apply(
        client_id,
        %{"state" => order_state, "timestamp" => timestamp} = msg,
        received_at,
        state
      ) do
    {:ok, last_received_at} = received_at |> Tai.Time.monotonic_to_date_time()

    with {:ok, type} <- transition_type(order_state) do
      {:ok, venue_timestamp} = Timex.parse(timestamp, @date_format)
      merged_attrs = msg
                     |> transition_attrs()
                     |> Map.put(:last_received_at, last_received_at)
                     |> Map.put(:last_venue_timestamp, venue_timestamp)
                     |> Map.put(:__type__, type)

      Tai.Orders.OrderTransitionWorker.apply(client_id, merged_attrs)
    else
      # TODO: Need to write unit test for :invalid_client_id
      {:error, :invalid_state} ->
        warn_unhandled(msg, last_received_at, state)
    end
  end

  def apply(_client_id, msg, received_at, state) do
    {:ok, last_received_at} = received_at |> Tai.Time.monotonic_to_date_time()
    warn_unhandled(msg, last_received_at, state)
  end

  defp warn_unhandled(msg, last_received_at, state) do
    TaiEvents.warning(%Tai.Events.StreamMessageUnhandled{
      venue_id: state.venue,
      msg: msg,
      received_at: last_received_at
    })
  end

  @canceled "-1"
  @pending "0"
  @partially_filled "1"
  @fully_filled "2"

  defp transition_type(s) when s == @canceled, do: {:ok, :cancel}
  defp transition_type(s) when s == @pending, do: {:ok, :open}
  defp transition_type(s) when s == @partially_filled, do: {:ok, :partial_fill}
  defp transition_type(s) when s == @fully_filled, do: {:ok, :fill}
  defp transition_type(_), do: {:error, :invalid_state}

  defp transition_attrs(%{"state" => order_state} = msg) do
    case order_state do
      s when s == @canceled ->
        %{}

      s when s == @pending or s == @partially_filled ->
        venue_order_id = Map.fetch!(msg, "order_id")
        size = Map.fetch!(msg, "size")
        cumulative_qty = msg |> filled() |> Decimal.new()
        leaves_qty = size |> Decimal.new() |> Decimal.sub(cumulative_qty)

        %{
          venue_order_id: venue_order_id,
          cumulative_qty: cumulative_qty,
          leaves_qty: leaves_qty,
        }

      s when s == @fully_filled ->
        venue_order_id = Map.fetch!(msg, "order_id")
        cumulative_qty = msg |> filled() |> Decimal.new()

        %{
          venue_order_id: venue_order_id,
          cumulative_qty: cumulative_qty
        }
    end
  end

  defp filled(msg) do
    Map.get(msg, "filled_size") || Map.fetch!(msg, "filled_qty")
  end
end
