module Hubstats
  class User < ActiveRecord::Base

    # Various checks that can be used to filter and find info about users.
    scope :with_id, lambda {|user_id| where(id: user_id.split(',')) if user_id}
    scope :only_active, -> { having("comment_count > 0 OR pull_request_count > 0 OR deploy_count > 0") }
    scope :is_developer, -> { having("pull_request_count > 0") }
    scope :is_reviewer, -> { having("comment_count > 0") }
    scope :with_contributions, lambda {|start_date, end_date, repo_id| with_all_metrics_repos(start_date, end_date, repo_id) if repo_id}

    # Public - Counts all of the deploys for selected user that occurred between the start_date and end_date.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of deploys
    scope :deploys_count, lambda {|start_date, end_date|
      select("hubstats_users.id as user_id")
       .select("IFNULL(COUNT(DISTINCT hubstats_deploys.id),0) AS deploy_count")
       .joins(sanitize_sql_array(["LEFT JOIN hubstats_deploys ON hubstats_deploys.user_id = hubstats_users.id AND (hubstats_deploys.deployed_at BETWEEN ? AND ?)", start_date, end_date]))
       .group("hubstats_users.id")
    }

    # Public - Counts all of the comments for selected user that occurred between the start_date and end_date.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of comments
    scope :comments_count, lambda {|start_date, end_date|
      select("hubstats_users.id as user_id")
       .select("IFNULL(COUNT(DISTINCT hubstats_comments.id),0) AS comment_count")
       .joins(sanitize_sql_array(["LEFT JOIN hubstats_comments ON hubstats_comments.user_id = hubstats_users.id AND (hubstats_comments.created_at BETWEEN ? AND ?)", start_date, end_date]))
       .group("hubstats_users.id")
    }
 
    # Public - Counts all of the merged pull requests for selected user that occurred between the start_date and end_date.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of pull requests
    scope :pull_requests_count, lambda {|start_date, end_date|
      select("hubstats_users.id as user_id")
      .select("IFNULL(COUNT(DISTINCT hubstats_pull_requests.id),0) AS pull_request_count")
      .joins(sanitize_sql_array(["LEFT JOIN hubstats_pull_requests ON hubstats_pull_requests.user_id = hubstats_users.id AND (hubstats_pull_requests.merged_at BETWEEN ? AND ?) AND hubstats_pull_requests.merged = '1'", start_date, end_date]))
      .group("hubstats_users.id")
    }

    # Public - Counts all of the merged pull requests, deploys, and comments that occurred between the start_date and end_date that belong to a user.
    #
    # start_date - the start of the date range
    # end_date - the end of the data range
    #
    # Returns - the count of deploys, pull requests, and comments
    scope :pull_comment_deploy_count, lambda {|start_date, end_date|
      select("hubstats_users.*, pull_request_count, comment_count, deploy_count")
      .joins("LEFT JOIN (#{pull_requests_count(start_date, end_date).to_sql}) AS pull_requests ON pull_requests.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{comments_count(start_date, end_date).to_sql}) AS comments ON comments.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{deploys_count(start_date, end_date).to_sql}) AS deploys ON deploys.user_id = hubstats_users.id")
      .group("hubstats_users.id")
    }

    # Public - Counts all of the deploys for selected user that occurred between the start_date and end_date and belong to the repos.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of deploys
    scope :deploys_count_by_repo, lambda {|start_date, end_date, repo_id|
      select("hubstats_users.id as user_id")
       .select("IFNULL(COUNT(DISTINCT hubstats_deploys.id),0) AS deploy_count")
       .joins(sanitize_sql_array(["LEFT JOIN hubstats_deploys ON hubstats_deploys.user_id = hubstats_users.id AND (hubstats_deploys.deployed_at BETWEEN ? AND ?) AND (hubstats_deploys.repo_id LIKE ?)", start_date, end_date, repo_id]))
       .group("hubstats_users.id")
    }

    # Public - Counts all of the comments for selected user that occurred between the start_date and end_date and belong to the repos.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of comments
    scope :comments_count_by_repo, lambda {|start_date, end_date, repo_id|
      select("hubstats_users.id as user_id")
      .select("COUNT(DISTINCT hubstats_comments.id) AS comment_count")
      .joins(sanitize_sql_array(["LEFT JOIN hubstats_comments ON hubstats_comments.user_id = hubstats_users.id AND (hubstats_comments.created_at BETWEEN ? AND ?) AND (hubstats_comments.repo_id LIKE ?)", start_date, end_date, repo_id]))
      .group("hubstats_users.id")
    }

    # Public - Counts all of the pull requests for selected user that occurred between the start_date and end_date and belong to the repos.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - count of pull requests
    scope :pull_requests_count_by_repo, lambda {|start_date, end_date, repo_id|
      select("hubstats_users.id as user_id")
      .select("IFNULL(COUNT(DISTINCT hubstats_pull_requests.id),0) AS pull_request_count")
      .joins(sanitize_sql_array(["LEFT JOIN hubstats_pull_requests ON hubstats_pull_requests.user_id = hubstats_users.id AND (hubstats_pull_requests.merged_at BETWEEN ? AND ?) AND (hubstats_pull_requests.repo_id LIKE ?) AND hubstats_pull_requests.merged = '1'", start_date, end_date, repo_id]))
      .group("hubstats_users.id")
    }

    # Public - Counts all of the merged pull requests, deploys, and comments that belong to a repository and belong to a user and occurred between 
    # the start_date and end_date.
    #
    # start_date - the start of the date range
    # end_date - the end of the data range
    #
    # Returns - the count of deploys, pull requests, and comments
    scope :pull_comment_deploy_count_by_repo, lambda {|start_date, end_date, repo_id|
      select("hubstats_users.*, pull_request_count, comment_count, deploy_count")
      .joins("LEFT JOIN (#{pull_requests_count_by_repo(start_date, end_date, repo_id).to_sql}) AS pull_requests ON pull_requests.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{comments_count_by_repo(start_date, end_date, repo_id).to_sql}) AS comments ON comments.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{deploys_count_by_repo(start_date, end_date, repo_id).to_sql}) AS deploys ON deploys.user_id = hubstats_users.id")
      .group("hubstats_users.id")
    }
    
    # Public - Counts all of the addtiions and deletions made from PRs by the selected user that have been merged between the start_date
    # and the end_date.
    #
    # start_date - the start of the date range
    # end_date - the end of the data range
    #
    # Returns - the additions and deletions
    scope :net_additions_count, lambda {|start_date, end_date|
      select("hubstats_users.id as user_id")
      .select("SUM(IFNULL(hubstats_pull_requests.additions, 0)) AS additions")
      .select("SUM(IFNULL(hubstats_pull_requests.deletions, 0)) AS deletions")
      .joins(sanitize_sql_array(["LEFT JOIN hubstats_pull_requests ON hubstats_pull_requests.user_id = hubstats_users.id AND (hubstats_pull_requests.merged_at BETWEEN ? AND ?) AND hubstats_pull_requests.merged = '1'", start_date, end_date]))
      .group("hubstats_users.id")
    }

    # Public - Joins all of the metrics together for selected repository: average additions and deletions, comments, pull requests, and deploys.
    # However, will only count those that also have something to do with the repos passed in.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - all of the stats about the user
    scope :with_all_metrics_repos, lambda {|start_date, end_date, repos|
      select("hubstats_users.*, deploy_count, pull_request_count, comment_count, additions, deletions")
      .joins("LEFT JOIN (#{net_additions_count(start_date, end_date).to_sql}) AS net_additions ON net_additions.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{pull_requests_count_by_repo(start_date, end_date, repos).to_sql}) AS pull_requests ON pull_requests.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{comments_count_by_repo(start_date, end_date, repos).to_sql}) AS comments ON comments.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{deploys_count_by_repo(start_date, end_date, repos).to_sql}) AS deploys ON deploys.user_id = hubstats_users.id")
      .group("hubstats_users.id")
    }

    # Public - Joins all of the metrics together for selected user: average additions and deletions, comments, pull requests, and deploys.
    # 
    # start_date - the start of the date range
    # end_date - the end of the data range
    # 
    # Returns - all of the stats about the user
    scope :with_all_metrics, lambda {|start_date, end_date|
      select("hubstats_users.*, deploy_count, pull_request_count, comment_count, additions, deletions")
      .joins("LEFT JOIN (#{net_additions_count(start_date, end_date).to_sql}) AS net_additions ON net_additions.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{pull_requests_count(start_date, end_date).to_sql}) AS pull_requests ON pull_requests.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{comments_count(start_date, end_date).to_sql}) AS comments ON comments.user_id = hubstats_users.id")
      .joins("LEFT JOIN (#{deploys_count(start_date, end_date).to_sql}) AS deploys ON deploys.user_id = hubstats_users.id")
      .group("hubstats_users.id")
    }
    
    validates :id, presence: true, uniqueness: true

    has_many :comments
    has_many :repos, :class_name => "Repo"
    has_many :pull_requests
    has_many :deploys
    has_and_belongs_to_many :teams, :join_table => 'hubstats_teams_users', :uniq => true

    # Public - Creates a new user form a GitHub webhook.
    #
    # github_user - the info from Github about the new or updated user
    #
    # Returns - the user 
    def self.create_or_update(github_user)
      github_user[:role] = github_user.delete :type  ##changing :type into :role
      github_user = github_user.to_h.with_indifferent_access unless github_user.is_a? Hash

      user_data = github_user.slice(*Hubstats::User.column_names.map(&:to_sym))
      
      user = Hubstats::User.where(:id => user_data[:id]).first_or_create(user_data)
      return user if user.update_attributes(user_data)
      Rails.logger.warn user.errors.inspect
    end

    # Public - If a repo_id is provided, will sort/filter the users based on the number of comments, deploys, and pull requests
    # on that repo within the start_date and end_date. If no repo_id is provided, will still sort, just considering all PRs and comments 
    # within the two dates.
    #
    # start_date - the start of the date range
    # end_date - the end of the data range
    # repo_id - the id of the repository (optional)
    #
    # Returns - the count of data that fulfills the sql queries 
    def self.with_pulls_or_comments_or_deploys(start_date, end_date, repo_id = nil)
      if repo_id
        pull_comment_deploy_count_by_repo(start_date, end_date, repo_id)
      else
        pull_comment_deploy_count(start_date, end_date)
      end
    end

    # Public - Designed so that the list of users can be ordered based on deploys, pulls, comments, net additions, or name.
    # If none of these are selected, then the default is to order by pull request count in descending order.
    #
    # order_params - the param of what the users should be sorted by
    #
    # Returns - the user data ordered
    def self.custom_order(order_params)
      if order_params
        order = order_params.include?('asc') ? "ASC" : "DESC"
        case order_params.split('-').first
        when 'deploys'
          order("deploy_count #{order}")
        when 'pulls'
          order("pull_request_count #{order}")
        when 'comments'
          order("comment_count #{order}")
        when 'netadditions'
          order("additions - deletions #{order}")
        when 'name'
          order("login #{order}")
        else
          order("pull_request_count #{order}")
        end
      else 
        order("pull_request_count DESC")
      end
    end

    # Public - Counts all of the pull requests for the users and sees if the count of PRs is greater than 0 (if they are a developer).
    # Then counts all of the developers.
    #
    # start_date - the starting date that we want to count the PRs from
    # end_date - the ending date that we want to count the PRs from
    #
    # Returns - the count of total users that have PRs > 0
    def self.count_active_developers(start_date, end_date)
      self.pull_requests_count(start_date, end_date).is_developer.to_a.count
    end

    # Public - Counts all of the comments for the users and sees if the count of comments is greater than 0 (if they are a reviewer).
    # Then counts all of the reviewers.
    #
    # start_date - the starting date that we want to count the comments from
    # end_date - the ending date that we want to count the comments from
    #
    # Returns - the count of total users that have comments > 0
    def self.count_active_reviewers(start_date, end_date)
      self.comments_count(start_date, end_date).is_reviewer.to_a.count
    end

    # Public - Gets the first team where the user is belongs to and where hubstats bool is true.
    #
    # Returns - the first team that the user belongs to where hubstats bool is true, if nothing
    # meets these qualifications, nil is returned
    def team
      teams.where(hubstats: true).first
    end

    # Public - Designed to make a path for the show page when a repository is selected.
    #
    # Returns - the show page of self.name
    def to_param
      self.login
    end
  end
end