defmodule Plausible.Teams.Users do
  @moduledoc """
  Users API accounting for teams.
  """

  import Ecto.Query

  alias Plausible.Repo
  alias Plausible.Teams

  def owned_teams(user) do
    Repo.all(teams_query(user, roles: :owner))
  end

  def teams(user, opts \\ []) do
    user
    |> teams_query(Keyword.merge([order_by: :name], opts))
    |> Repo.all()
    |> Repo.preload(:owners)
  end

  def teams_query(user, opts \\ []) do
    order_by = Keyword.get(opts, :order_by, :name)
    roles = Keyword.get(opts, :roles)
    identifier = Keyword.get(opts, :identifier)

    query =
      from(
        tm in Teams.Membership,
        as: :team_membership,
        inner_join: t in assoc(tm, :team),
        as: :team,
        where: tm.user_id == ^user.id,
        where: tm.role != :guest,
        select: t
      )

    query =
      if roles do
        where(query, [team_membership: tm], tm.role in ^List.wrap(roles))
      else
        query
      end

    query =
      if identifier do
        where(query, [team: team], team.identifier == ^identifier)
      else
        query
      end

    case order_by do
      :name ->
        order_by(query, [team: t], [t.name, t.id])

      :id_desc ->
        order_by(query, [team: t], desc: t.id)

      _ ->
        query
    end
  end

  def team_member?(user, opts \\ []) do
    excluded_team_ids = Keyword.get(opts, :except, [])
    only_setup? = Keyword.get(opts, :only_setup?, false)

    query =
      from(
        tm in Teams.Membership,
        where: tm.user_id == ^user.id,
        where: tm.role != :guest,
        where: tm.team_id not in ^excluded_team_ids
      )

    query =
      if only_setup? do
        query
        |> join(:inner, [tm], t in assoc(tm, :team), as: :team)
        |> where([team: t], t.setup_complete == true)
      else
        query
      end

    Repo.exists?(query)
  end

  def has_sites?(user, opts \\ []) do
    include_pending? = Keyword.get(opts, :include_pending?, false)

    sites_query =
      from(
        s in Plausible.Site,
        where: s.team_id == parent_as(:site_parent).team_id,
        select: 1
      )

    # NOTE: Provided guest team memberships are consistently pruned,
    # mere presence of guest team membership means there are sites
    # associated with it, so querying for team membership alone
    # should be enough.
    team_member_query =
      from(
        tm in Teams.Membership,
        as: :site_parent,
        where: exists(sites_query),
        where: tm.user_id == ^user.id,
        select: 1
      )

    query =
      if include_pending? do
        site_transfer_query =
          from(
            st in Teams.SiteTransfer,
            where: st.email == ^user.email,
            select: 1
          )

        # NOTE: The same principle applies to guest team invitations,
        # as it's pruned as well when there are no more guest invitations
        # associated with it.
        member_invitation_query =
          from(
            ti in Teams.Invitation,
            as: :site_parent,
            where: exists(sites_query),
            where: ti.email == ^user.email,
            select: 1
          )

        team_member_query
        |> union_all(^site_transfer_query)
        |> union_all(^member_invitation_query)
      else
        team_member_query
      end

    Repo.exists?(query)
  end

  def owns_sites?(user, opts \\ []) do
    include_pending? = Keyword.get(opts, :include_pending?, false)
    only_team = Keyword.get(opts, :only_team)

    sites_query =
      from(
        s in Plausible.Site,
        where: s.team_id == parent_as(:site_parent).team_id,
        select: 1
      )

    owner_query =
      from(
        tm in Teams.Membership,
        as: :site_parent,
        where: exists(sites_query),
        where: tm.user_id == ^user.id,
        where: tm.role == :owner,
        select: 1
      )

    owner_query =
      if only_team do
        where(owner_query, [tm], tm.team_id == ^only_team.id)
      else
        owner_query
      end

    query =
      if include_pending? do
        site_transfer_query =
          from(
            st in Teams.SiteTransfer,
            where: st.email == ^user.email,
            select: 1
          )

        owner_invitation_query =
          from(
            ti in Teams.Invitation,
            as: :site_parent,
            where: exists(sites_query),
            where: ti.email == ^user.email,
            where: ti.role == :owner,
            select: 1
          )

        owner_query
        |> union_all(^site_transfer_query)
        |> union_all(^owner_invitation_query)
      else
        owner_query
      end

    Repo.exists?(query)
  end
end
