defmodule Moba.Admin.Matches do
  @moduledoc """
  Admin functions for managing Matches, mostly generated by Torch package.
  """

  alias Moba.{Repo, Game}
  alias Game.Schema.Match
  alias Game.Query.{ItemQuery, SkillQuery}

  import Ecto.Query

  def match_stats do
    %{
      "pvp" => filtered_stats(nil),
      "elite" => filtered_stats([1, 2]),
      "immortals" => filtered_stats(2),
      "shadows" => filtered_stats(1),
      "plebs" => filtered_stats(0)
    }
  end

  defp filtered_query(tiers) when is_list(tiers) do
    from(m in Match, join: p in assoc(m, :player), where: p.pvp_tier in ^tiers)
  end

  defp filtered_query(tier) do
    if tier do
      from(m in Match, join: p in assoc(m, :player), where: p.pvp_tier == ^tier)
    else
      Match
    end
  end

  defp filtered_stats(pvp_tier) do
    query = filtered_query(pvp_tier)
    matches = Repo.all(from(m in query, where: m.phase == "scored"))

    if length(matches) > 0 do
      won_matches = Enum.filter(matches, &(&1.winner_id == &1.player_id))
      total_winrate = Float.round(length(won_matches) / length(matches) * 100, 2)
      scores = hero_scores(matches)

      %{
        skills: skill_winrates(scores),
        avatars: avatar_winrates(scores),
        items: item_winrates(scores),
        winrate: total_winrate,
        total: length(matches)
      }
    else
      %{}
    end
  end

  defp hero_scores(matches) do
    heroes =
      matches
      |> Enum.reduce([], fn match, acc ->
        acc ++ match.player_picks
      end)
      |> Game.get_heroes()
      |> Enum.filter(& &1)

    Enum.reduce(matches, %{}, fn match, acc ->
      Enum.reduce(match.player_picks, acc, fn pick_id, inner_acc ->
        {win, loss} = Map.get(inner_acc, pick_id) || {0, 0}

        if match.winner_id == match.player_id do
          Map.put(inner_acc, pick_id, {win + 1, loss})
        else
          Map.put(inner_acc, pick_id, {win, loss + 1})
        end
      end)
    end)
    |> Enum.map(fn {pick_id, score} ->
      hero = Enum.find(heroes, &(&1.id == pick_id))

      if hero do
        {hero.avatar.code, hero, score}
      else
        {nil, %{skills: [], items: []}, score}
      end
    end)
  end

  defp skill_winrates(scores) do
    skills =
      SkillQuery.base_canon() |> SkillQuery.normals() |> SkillQuery.enabled() |> SkillQuery.with_level(5) |> Repo.all()

    Enum.reduce(skills, %{}, fn skill, acc ->
      {wins, losses} =
        Enum.reduce(scores, {0, 0}, fn {_, hero, {win, loss}}, {acc_win, acc_loss} = acc ->
          hero_skill_codes = Enum.map(hero.skills, & &1.code)

          if Enum.member?(hero_skill_codes, skill.code) do
            {acc_win + win, acc_loss + loss}
          else
            acc
          end
        end)

      total = if wins + losses == 0, do: 1, else: wins + losses
      winrate = Float.round(wins / total * 100, 2)

      Map.put(acc, skill, {winrate, wins + losses})
    end)
  end

  defp avatar_winrates(scores) do
    avatars = Game.list_avatars()

    Enum.reduce(avatars, %{}, fn %{code: code} = avatar, acc ->
      {wins, losses} =
        Enum.reduce(scores, {0, 0}, fn
          {^code, _, {win, loss}}, {acc_win, acc_loss} -> {acc_win + win, acc_loss + loss}
          {_, _, _}, inner_acc -> inner_acc
        end)

      total = if wins + losses == 0, do: 1, else: wins + losses
      winrate = Float.round(wins / total * 100, 2)

      Map.put(acc, avatar, {winrate, wins + losses})
    end)
  end

  defp item_winrates(scores) do
    items =
      ItemQuery.base_canon() |> ItemQuery.enabled() |> ItemQuery.with_rarities(["epic", "legendary"]) |> Repo.all()

    Enum.reduce(items, %{}, fn item, acc ->
      {wins, losses} =
        Enum.reduce(scores, {0, 0}, fn {_, hero, {win, loss}}, {acc_win, acc_loss} = acc ->
          hero_item_codes = Enum.map(hero.items, & &1.code)

          if Enum.member?(hero_item_codes, item.code) do
            {acc_win + win, acc_loss + loss}
          else
            acc
          end
        end)

      total = if wins + losses == 0, do: 1, else: wins + losses
      winrate = Float.round(wins / total * 100, 2)

      Map.put(acc, item, {winrate, wins + losses})
    end)
  end
end
