# frozen_string_literal: true

module RedmineUxToolkit
  module Services
    class TeamWorkload
      STALE_DAYS_DEFAULT = 14
      STALE_DAYS_RANGE = 1..365
      Row = Struct.new(
        :user,
        :open_count,
        :overdue_count,
        :due_today_count,
        :stale_count,
        :high_priority_count,
        keyword_init: true
      )

      attr_reader :user, :project, :project_ids, :include_closed, :active_users_only

      def initialize(user:, project: nil, project_ids: nil, include_closed: false, active_users_only: true)
        @user = user
        @project = project
        @project_ids = Array(project_ids).compact
        @include_closed = include_closed
        @active_users_only = active_users_only
      end

      def rows
        user_ids = assignee_ids
        return [] if user_ids.empty?

        users = users_by_id(user_ids)
        count_scope = issues_for_users(users.keys)

        open_counts = grouped_count(open_issues_for_users(users.keys))
        overdue_counts = grouped_count(count_scope.where("#{Issue.table_name}.due_date < ?", today))
        due_today_counts = grouped_count(count_scope.where(due_date: today))
        stale_counts = grouped_count(count_scope.where("#{Issue.table_name}.updated_on < ?", stale_cutoff))
        high_priority_counts = high_priority_ids.any? ? grouped_count(count_scope.where(priority_id: high_priority_ids)) : {}

        users.values.map do |assignee|
          Row.new(
            user: assignee,
            open_count: open_counts[assignee.id].to_i,
            overdue_count: overdue_counts[assignee.id].to_i,
            due_today_count: due_today_counts[assignee.id].to_i,
            stale_count: stale_counts[assignee.id].to_i,
            high_priority_count: high_priority_counts[assignee.id].to_i
          )
        end
      end

      def stale_days
        settings = Setting.plugin_redmine_ux_toolkit || {}
        days = settings.fetch('stale_issue_days', STALE_DAYS_DEFAULT).to_i

        STALE_DAYS_RANGE.cover?(days) ? days : STALE_DAYS_DEFAULT
      end

      def high_priority_ids
        @high_priority_ids ||= begin
          priorities = IssuePriority.active.to_a
          default_priority = IssuePriority.default || priorities.first
          return [] if priorities.empty?

          ids = if default_priority
                  priorities.select {|priority| priority.position.to_i > default_priority.position.to_i}.map(&:id)
                else
                  []
                end

          ids.presence || [priorities.max_by {|priority| priority.position.to_i }.id]
        end
      end

      private

      def assignee_ids
        scope = issue_scope
        scope = scope.where("#{Issue.table_name}.assigned_to_id IS NOT NULL")
        scope = scope.where("#{Issue.table_name}.status_id NOT IN (?)", closed_status_ids) unless include_closed || closed_status_ids.empty?
        scope.distinct.pluck(:assigned_to_id).compact
      end

      def users_by_id(user_ids)
        scope = User.where(id: user_ids)
        scope = scope.active if active_users_only && scope.respond_to?(:active)
        scope.order(:lastname, :firstname, :login).to_a.index_by(&:id)
      end

      def issues_for_users(user_ids)
        scope = issue_scope.where(assigned_to_id: user_ids)
        scope = scope.where("#{Issue.table_name}.status_id NOT IN (?)", closed_status_ids) unless include_closed || closed_status_ids.empty?
        scope
      end

      def open_issues_for_users(user_ids)
        scope = issue_scope.where(assigned_to_id: user_ids)
        closed_status_ids.empty? ? scope : scope.where("#{Issue.table_name}.status_id NOT IN (?)", closed_status_ids)
      end

      def issue_scope
        scope = Issue.visible(user)
        return scope.where(project_id: project.id) if project
        return scope.where(project_id: project_ids) if project_ids.any?

        scope
      end

      def grouped_count(scope)
        scope.group(:assigned_to_id).count
      end

      def closed_status_ids
        @closed_status_ids ||= IssueStatus.where(is_closed: true).pluck(:id)
      end

      def stale_cutoff
        Time.zone.now - stale_days.days
      end

      def today
        @today ||= RedmineUxToolkit::Compatibility.user_today(user)
      end
    end
  end
end
