defmodule Mongo.ServerDescription do
  @moduledoc false

  # see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription
  @type type ::
          :standalone
          | :mongos
          | :possible_primary
          | :rs_primary
          | :rs_secondary
          | :rs_arbiter
          | :rs_other
          | :rs_ghost
          | :unknown
  @type t :: %{
          address: String.t() | nil,
          error: String.t() | nil,
          round_trip_time: non_neg_integer | nil,
          last_write_date: DateTime.t(),
          op_time: BSON.ObjectId.t() | nil,
          type: type,
          min_wire_version: non_neg_integer,
          max_wire_version: non_neg_integer,
          me: String.t() | nil,
          hosts: [String.t()],
          passives: [String.t()],
          arbiters: [String.t()],
          tag_set: %{String.t() => String.t()},
          set_name: String.t() | nil,
          set_version: non_neg_integer | nil,
          election_id: BSON.ObjectId.t() | nil,
          primary: String.t() | nil,
          last_update_time: non_neg_integer
        }

  def defaults(map \\ %{}) do
    Map.merge(
      %{
        address: "localhost:27017",
        error: nil,
        round_trip_time: nil,
        last_write_date: nil,
        op_time: nil,
        type: :unknown,
        min_wire_version: 0,
        max_wire_version: 0,
        me: nil,
        hosts: [],
        passives: [],
        arbiters: [],
        tag_set: %{},
        set_name: nil,
        set_version: nil,
        election_id: nil,
        primary: nil,
        last_update_time: 0
      },
      map
    )
  end

  def from_is_master_error(last_server_description, error) do
    defaults(%{
      address: last_server_description.address,
      error: error
    })
  end

  # see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#parsing-an-ismaster-response
  def from_is_master(last_description, rtt, finish_time, is_master_reply) do
    last_rtt = last_description.round_trip_time || rtt

    defaults(%{
      address: last_description.address,
      round_trip_time: round(0.2 * rtt + 0.8 * last_rtt),
      type: determine_server_type(is_master_reply),
      last_write_date:
        get_in(
          is_master_reply,
          ["lastWrite", "lastWriteDate"]
        ),
      op_time: get_in(is_master_reply, ["lastWrite", "opTime"]),
      last_update_time: finish_time,
      min_wire_version: is_master_reply["minWireVersion"] || 0,
      max_wire_version: is_master_reply["maxWireVersion"] || 0,
      me: is_master_reply["me"],
      hosts: (is_master_reply["hosts"] || []) |> Enum.map(&String.downcase/1),
      passives:
        (is_master_reply["passives"] || [])
        |> Enum.map(&String.downcase/1),
      arbiters:
        (is_master_reply["arbiters"] || [])
        |> Enum.map(&String.downcase/1),
      tag_set: is_master_reply["tags"] || %{},
      set_name: is_master_reply["setName"],
      set_version: is_master_reply["setVersion"],
      election_id: is_master_reply["electionId"],
      primary: is_master_reply["primary"]
    })
  end

  # see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#type
  defp determine_server_type(%{"ok" => n}) when n != 1, do: :unknown
  defp determine_server_type(%{"msg" => "isdbgrid"}), do: :mongos
  defp determine_server_type(%{"isreplicaset" => true}), do: :rs_ghost

  defp determine_server_type(%{"setName" => set_name} = is_master_reply) when set_name != nil do
    case is_master_reply do
      %{"ismaster" => true} ->
        :rs_primary

      %{"secondary" => true} ->
        :rs_secondary

      %{"arbiterOnly" => true} ->
        :rs_arbiter

      _ ->
        :rs_other
    end
  end

  defp determine_server_type(_), do: :standalone
end
