module Types::Entities
  class UserType < Types::BaseObject
    field :id, Int, null: false
    field :name, String, null: true
    field :url, String, null: true
    field :username, String, null: false
    field :role, Types::Entities::UserRoleType, null: false
    field :bio, String, null: true
    field :country, Types::Entities::CountryType, null: true
    field :organizations, [ Types::Entities::OrganizationType ], null: false
    field :organizations_with_endorsement_privileges, [ Types::Entities::OrganizationType ], null: false
    field :events, Types::Entities::EventType.connection_type, null: false
    field :display_name, String, null: false
    field :area_of_expertise, Types::AreaOfExpertiseType, null: true
    field :orcid, String, null: true
    field :twitter_handle, String, null: true
    field :facebook_profile, String, null: true
    field :linkedin_profile, String, null: true
    field :stats_hash, Types::StatsType, null: false
    field :most_recent_conflict_of_interest_statement, Types::Entities::CoiType, null: true
    field :most_recent_event, Types::Entities::EventType, null: true
    field :most_recent_activity_timestamp, GraphQL::Types::ISO8601DateTime, null: true
    field :most_recent_organization_id, Int, null: true
    field :ranks, Types::Entities::RanksType, null: false
    field :email, String, null: true
    field :api_keys, [ Types::ApiKeyType ], null: false
    field :join_date, GraphQL::Types::ISO8601DateTime, null: true

    profile_image_sizes = [ 256, 128, 64, 32, 18, 12 ]
    field :profile_image_path, String, null: true do
      argument :size, Int, required: false, default_value: 56,
        validates: {
          inclusion: {
            in: profile_image_sizes,
            message: "Size must be one of [#{profile_image_sizes.join(',')}]",
          },
        }
    end

    field :notifications, Types::Entities::NotificationType.connection_type, null: true do
      description "Filterable list of notifications for the logged in user."
      type_desc = "Filter the response to include only notifications of a certain type (ex: mentions)."
      sub_desc = "Filter the response to include only notifications generated by a particular subscription."
      event_desc = "Filter the response to include only notifications generated by certain actions (ex: commenting)."

      argument :notification_type, Types::NotificationReasonType, required: false, description: type_desc
      argument :event_type, Types::Events::EventActionType, required: false, description: event_desc
      argument :subscription_id, Int, required: false, description: sub_desc
      argument :include_seen, Boolean, required: false, default_value: false, description: sub_desc

      def authorized?(object, args, context)
        object.id == context[:current_user]&.id
      end
    end

    def email
      # You can only fetch your own email
      if object.id == context[:current_user]&.id
        object.email
      else
        nil
      end
    end

    def organizations
      Loaders::AssociationLoader.for(User, :organizations).load(object)
    end

    def organizations_with_endorsement_privileges
      Loaders::AssociationLoader.for(User, :organizations_with_endorsement_privileges).load(object)
    end

    def notifications(notification_type: nil, subscription_id: nil, event_type: nil, include_seen:)
      allowed_filters =  {
        type: Notification.types[notification_type],
        subscription_id: subscription_id,
      }
      selected_filters = allowed_filters.select { |_, v| v.present? }

      if !include_seen
        selected_filters[:seen] = false
      end

      if event_type.present?
        selected_filters[:events] = {}
        selected_filters[:events][:action] = event_type
      end

      object.notifications
        .joins(:event)
        .where(selected_filters)
        .order("notifications.created_at DESC")
    end

    def events
      Loaders::AssociationLoader.for(User, :events).load(object)
    end

    def country
      Loaders::AssociationLoader.for(User, :country).load(object)
    end

    def profile_image_path(size:)
      Loaders::ActiveStorageLoader.for(:User, :profile_image).load(object.id).then do |image|
        if image
          Rails.application.routes.url_helpers.url_for(
            image.variant(resize_to_limit: [ size, size ]).processed.url
          )
        else
        end
      end
    end

    def most_recent_conflict_of_interest_statement
      Loaders::AssociationLoader.for(User, :most_recent_conflict_of_interest_statement).load(object)
    end

    def most_recent_event
      Loaders::AssociationLoader.for(User, :most_recent_event).load(object)
    end

    def stats_hash
      Rails.cache.fetch("user_stats_#{object.id}", expires_in: 1.hour) do
        object.stats_hash
      end
    end

    def ranks
      {
        moderation_rank: Leaderboard.single_user_query(object.id, Leaderboard.moderation_actions),
        comments_rank: Leaderboard.single_user_query(object.id, Leaderboard.comment_actions),
        submissions_rank: Leaderboard.single_user_query(object.id, Leaderboard.submission_actions),
        revisions_rank: Leaderboard.single_user_query(object.id, Leaderboard.revision_actions),
      }
    end

    def api_keys
      if object.id == context[:current_user]&.id
        object.api_keys.where(revoked: false)
      else
        []
      end
    end

    def join_date
      # In the case of merged accounts, the created_at field on the user record
      # may be misleading. Instead use the first known authorization record.
      object.authorizations
        .order("created_at ASC")
        .pluck(:created_at)
        .first
    end
  end
end
