# frozen_string_literal: true

#
# Copyright (C) 2017 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

module SIS
  class AdminImporter < BaseImporter
    def process
      importer = Work.new(@batch, @root_account, @logger)

      AccountUser.suspend_callbacks(:clear_user_cache) do
        User.skip_updating_account_associations do
          yield importer
        end
      end

      User.update_account_associations(importer.account_users_to_update_associations.to_a)
      user_ids = []
      importer.account_users_to_set_batch_id.to_a.in_groups_of(1000, false) do |admins|
        user_ids += AccountUser.where(id: admins).distinct.pluck(:user_id)
        AccountUser.where(id: admins).update_all(sis_batch_id: @batch.id, updated_at: Time.now.utc)
      end
      User.clear_cache_keys(user_ids, :account_users)
      SisBatchRollBackData.bulk_insert_roll_back_data(importer.roll_back_data)

      importer.success_count
    end

    class Work
      attr_accessor :success_count,
                    :roll_back_data,
                    :account_users_to_update_associations,
                    :account_users_to_set_batch_id

      def initialize(batch, root_account, logger)
        @batch = batch
        @root_account = root_account
        @account = root_account
        @logger = logger
        @success_count = 0
        @roll_back_data = []
        @account_users_to_update_associations = Set.new
        @account_users_to_set_batch_id = Set.new
        @account_roles_by_account_id = {}
      end

      def process_admin(user_id: nil, account_id: nil, role_id: nil, role: nil, status: nil, root_account: nil)
        raise ImportError, "No user_id given for admin" if user_id.blank?
        raise ImportError, "No status given for admin" if status.blank?
        raise ImportError, "No role_id or role given for admin" if role.blank? && role_id.blank?

        state = status.downcase.strip
        raise ImportError, "Invalid status #{status} for admin" unless %w[active deleted].include? state
        return if @batch.skip_deletes? && state == "deleted"

        get_account(account_id)
        raise ImportError, "Invalid account_id given for admin" unless @account

        get_role(role_id, role)
        raise ImportError, "Invalid role '#{role}' for admin" if role.present? && !@role
        raise ImportError, "Invalid role_id '#{role_id}' for admin" if role_id.present? && !@role

        the_root_account = root_account_from_id(root_account) if root_account
        raise ImportError, "Invalid or unknown user_id '#{user_id}' for admin" if root_account && !the_root_account

        the_root_account ||= @root_account

        user = get_user(user_id, the_root_account)
        raise ImportError, "Invalid or unknown user_id '#{user_id}' for admin" unless user

        if state == "deleted" && user.id == @batch&.user_id && @account == @root_account
          raise ImportError, "Can't remove yourself user_id '#{user_id}'"
        end

        create_or_find_admin(user, state)
        user.clear_adminable_accounts_cache!
        @success_count += 1
      end

      def create_or_find_admin(user, state)
        case state
        when "active"
          admin = @account.account_users.where(user:, role: @role).first_or_initialize
          admin.workflow_state = state
        when "deleted"
          admin = @account.account_users.where(user:, role: @role).where.not(sis_batch_id: nil).take
          return unless admin

          admin.current_user = @batch.user if @batch&.user
          admin.workflow_state = state
        end

        if admin.new_record? || admin.workflow_state_changed?
          @account_users_to_update_associations.add(user.id)
          admin.save!
          data = SisBatchRollBackData.build_data(sis_batch: @batch, context: admin)
          @roll_back_data << data if data
        end
        @account_users_to_set_batch_id.add(admin.id)
      end

      def get_account(account_id)
        @account = nil unless @account&.sis_source_id == account_id
        @account ||= @root_account.all_accounts.active.find_by(sis_source_id: account_id) if account_id.present?
        @account ||= @root_account if account_id.blank?
      end

      def get_user(user_id, root_account)
        pseudonym = root_account.pseudonyms.active.find_by(sis_user_id: user_id)
        user = pseudonym.user if pseudonym
        user
      end

      def root_account_from_id(_root_account_sis_id)
        nil
      end

      def get_role(role_id, role)
        # cache available account roles for this account
        @account_roles_by_account_id[@account.id] ||= @account.available_account_roles

        @role = nil
        @role = @account_roles_by_account_id[@account.id].detect { |r| r.id.to_s == role_id } if role_id
        @role ||= @account_roles_by_account_id[@account.id].detect { |r| r.name == role }
      end
    end
  end
end
