defmodule MoonWeb.MockDB.Users do
  @moduledoc false

  use GenServer

  alias MoonWeb.MockDB.{Countries, Sites}
  alias MoonWeb.MockDB.Utils

  @process_name :mock_users

  # client
  def start_link do
    GenServer.start_link(__MODULE__, [], name: @process_name)
  end

  def list(
        args = %{
          filter: %{id: _, country: _, site: _},
          sort: _
        }
      ) do
    this_process() |> GenServer.call({:list, args})
  end

  def list_all, do: this_process() |> GenServer.call(:list_all)

  def search_by_usernames(""), do: []

  def search_by_usernames(search_text) do
    this_process()
    |> GenServer.call({:search_usernames, search_text})
  end

  # server
  def init(_args) do
    countries = Countries.list_all()

    {:ok,
     %{
       all:
         0..1000
         |> Enum.flat_map(fn _ ->
           Sites.random_list()
           |> Enum.map(fn site ->
             %{
               id: Utils.random_id(),
               name: Faker.Person.name(),
               email: Faker.Internet.email(),
               country: Faker.Util.pick(countries).name,
               username: Faker.Internet.user_name(),
               site: site.name,
               signup_at: Faker.DateTime.backward(120)
             }
           end)
         end)
     }}
  end

  def handle_call(:list_all, _from, state) do
    {:reply, state.all, state}
  end

  def handle_call({:list, args}, _from, state) do
    %{filter: %{id: id, country: country, site: site}, sort: sort} = args

    results =
      state.all
      |> Enum.sort(fn a, b -> sort_users(a, b, sort) end)
      |> Enum.filter(fn user ->
        (Enum.empty?(id) or Enum.member?(id, user.id)) and
          (Enum.empty?(site) or Enum.member?(site, user.site)) and
          (Enum.empty?(country) or Enum.member?(country, user.country))
      end)

    {:reply, results, state}
  end

  def handle_call({:search_usernames, search_text}, _from, state) do
    results =
      state.all
      |> Enum.filter(&String.contains?(&1.username, search_text))

    {:reply, results, state}
  end

  defp sort_users(a, b, sort) do
    case sort do
      %{id: :asc} -> a.id < b.id
      %{id: :desc} -> a.id >= b.id
      %{username: :asc} -> a.username < b.username
      %{username: :desc} -> a.username >= b.username
      _ -> true
    end
  end

  # helpers
  defp this_process do
    Process.whereis(@process_name)
  end
end
