module Katello
  class Api::V2::RepositorySetsController < Api::V2::ApiController
    respond_to :json

    include Katello::Concerns::FilteredAutoCompleteSearch

    before_action :set_readable_product_scope, only: [:index, :show, :available_repositories, :auto_complete_search]
    before_action :set_editable_product_scope, only: [:enable, :disable]
    before_action :find_product
    before_action :custom_product?
    before_action :setup_params
    before_action :find_authorized_activation_key, :only => [:index, :auto_complete_search]
    before_action :find_authorized_host, :only => [:index, :auto_complete_search]
    before_action :find_organization
    before_action :find_product_content, :except => [:index, :auto_complete_search]
    before_action :check_airgapped, :only => [:available_repositories, :enable, :disable]

    resource_description do
      api_version "v2"
    end

    api :GET, "/repository_sets", N_("List repository sets.")
    api :GET, "/products/:product_id/repository_sets", N_("List repository sets for a product.")
    param :product_id, :number, :required => false, :desc => N_("ID of a product to list repository sets from")
    param :name, String, :required => false, :desc => N_("Repository set name to search on")
    param :enabled, :bool, :required => false, :desc => N_("If true, only return repository sets that have been enabled. Defaults to false")
    param :with_active_subscription, :bool, :required => false, :desc => N_("If true, only return repository sets that are associated with an active subscriptions")
    param :organization_id, :number, :desc => N_("organization identifier"), :required => false
    param :with_custom, :bool, :required => false, :desc => N_("If true, return custom repository sets along with redhat repos. Will be ignored if repository_type is supplied.")
    param :activation_key_id, :number, :desc => N_("activation key identifier"), :required => false
    param :host_id, :number, :desc => N_("Id of the host"), :required => false
    param :content_access_mode_all, :bool, :desc => N_("Get all content available, not just that provided by subscriptions."), deprecated: true, default: true
    param :content_access_mode_env, :bool, :desc => N_("Limit content to just that available in the host's or activation key's content view version and lifecycle environment.")
    param :status, [:enabled, :disabled, :overridden],
                                  :desc => N_("Limit content to enabled / disabled / overridden"),
                                  :required => false
    param :repository_type, [:redhat, :custom], :desc => N_("Limit content to Red Hat / custom"), :required => false

    param_group :search, Api::V2::ApiController
    add_scoped_search_description_for(Katello::ProductContent)
    def index
      collection = scoped_search(index_relation, :name, :asc, :resource_class => Katello::ProductContent, :custom_sort => ->(relation) { custom_sort_results(relation) })
      pcf = ProductContentFinder.wrap_with_overrides(
        product_contents: collection[:results],
        overrides: @consumable&.content_overrides)
      collection[:results] = pcf
      respond(:collection => collection)
    end

    api :GET, "/repository_sets/:id", N_("Get info about a repository set")
    api :GET, "/products/:product_id/repository_sets/:id", N_("Get info about a repository set")
    param :id, :number, :required => true, :desc => N_("ID of the repository set")
    param :product_id, :number, :required => false, :desc => N_("ID of a product to list repository sets from")
    param :organization_id, :number, :desc => N_("organization identifier"), :required => false
    def show
      respond :resource => @product_content
    end

    api :GET, "/repository_sets/:id/available_repositories", N_("Get list of available repositories for the repository set")
    api :GET, "/products/:product_id/repository_sets/:id/available_repositories", N_("Get list of available repositories for the repository set")
    param :id, :number, :required => true, :desc => N_("ID of the repository set")
    param :product_id, :number, :required => false, :desc => N_("ID of a product to list repository sets from")
    param :organization_id, :number, :desc => N_("organization identifier"), :required => false
    def available_repositories
      scan_cdn = sync_task(::Actions::Katello::RepositorySet::ScanCdn, @product, @product_content.content.cp_content_id)
      repos = scan_cdn.output[:results]

      repos = repos.select do |repo|
        if repo[:path].include?('kickstart') && repo[:substitutions][:releasever].present?
          repo[:substitutions][:releasever].include?('.') || repo[:enabled]
        else
          true
        end
      end

      sorted_repos = repos.sort_by do |repo|
        major, minor = repo[:substitutions][:releasever].nil? ? [1000, 1000] : repo[:substitutions][:releasever].split('.').map(&:to_i)
        major = major == 0 ? 1000 : major
        minor = minor.nil? ? 1000 : minor
        arch = repo[:substitutions][:basearch].nil? ? "" : repo[:substitutions][:basearch]

        [arch, major, minor]
      end

      collection = {
        :results => sorted_repos.reverse,
        :subtotal => repos.size,
        :total => repos.size,
      }

      respond_for_index :collection => collection
    end

    api :PUT, "/repository_sets/:id/enable", N_("Enable a repository from the set")
    api :PUT, "/products/:product_id/repository_sets/:id/enable", N_("Enable a repository from the set")
    param :id, :number, :required => true, :desc => N_("ID of the repository set to enable")
    param :product_id, :number, :required => false, :desc => N_("ID of the product containing the repository set")
    param :basearch, String, :required => false, :desc => N_("Basearch to enable")
    param :releasever, String, :required => false, :desc => N_("Releasever to enable")
    param :organization_id, :number, :desc => N_("organization identifier"), :required => false
    def enable
      task = sync_task(::Actions::Katello::RepositorySet::EnableRepository, @product, @product_content.content, substitutions)
      respond_for_async :resource => task
    end

    api :PUT, "/repository_sets/:id/disable", N_("Disable a repository from the set")
    api :PUT, "/products/:product_id/repository_sets/:id/disable", N_("Disable a repository from the set")
    param :id, :number, :required => true, :desc => N_("ID of the repository set to disable")
    param :repository_id, :number, :required => false, :desc => N_("ID of the repository within the set to disable")
    param :product_id, :number, :required => false, :desc => N_("ID of the product containing the repository set")
    param :basearch, String, :required => false, :desc => N_("Basearch to disable")
    param :releasever, String, :required => false, :desc => N_("Releasever to disable")
    param :organization_id, :number, :desc => N_("organization identifier"), :required => false
    def disable
      task = sync_task(::Actions::Katello::RepositorySet::DisableRepository, @product, @product_content.content, substitutions)
      respond_for_async :resource => task
    end

    protected

    def resource_class
      Katello::ProductContent
    end

    def index_relation
      if @product.nil?
        authorized_product_contents = Katello::ProductContent.joins(:product).merge(@product_scope)
        relation = @organization.product_contents.merge(authorized_product_contents).displayable
      else
        relation = @product.displayable_product_contents
      end
      if %w(custom redhat).include?(params[:repository_type])
        relation = relation.send(params[:repository_type].to_sym)
      end

      if ::Foreman::Cast.to_bool(params[:enabled])
        relation = relation.enabled(@organization)
      elsif ::Foreman::Cast.to_bool(params[:with_active_subscription])
        relation = relation.with_valid_subscription(@organization)
      else
        relation = relation.where(:id => Katello::ProductContent.with_valid_subscription(@organization)).or(
            relation.where(:id => Katello::ProductContent.enabled(@organization)))
      end

      relation = relation.where(Katello::Content.table_name => {:name => params[:name]}) if params[:name].present?
      # ignore with_custom if repository_type is specified
      if params[:repository_type].blank? && !::Foreman::Cast.to_bool(params[:with_custom])
        relation = relation.redhat
      end
      index_relation_with_consumable_overrides(relation)
    end

    def index_relation_with_consumable_overrides(relation)
      return relation if @consumable.blank?

      content_access_mode_all = ::Foreman::Cast.to_bool(params[:content_access_mode_all])
      content_access_mode_env = ::Foreman::Cast.to_bool(params[:content_access_mode_env])

      content_finder = ProductContentFinder.new(
          :match_subscription => !content_access_mode_all,
          :match_environment => content_access_mode_env,
          :consumable => @consumable)
      unfiltered = relation.merge(content_finder.product_content)
      return unfiltered unless params[:status] || params[:repository_type]
      filtered_ids = ProductContentFinder.wrap_with_overrides(
        product_contents: unfiltered,
        overrides: @consumable&.content_overrides,
        status: params[:status],
        repository_type: params[:repository_type]
      ).map(&:id).uniq
      unfiltered.where(id: filtered_ids)
    end

    def find_product_content
      if @product.present?
        @product_content = @product.product_content_by_id(params[:id])
      else
        content = Katello::Content.where(cp_content_id: params[:id], organization: @organization)
        authorized_product_contents = Katello::ProductContent.joins(:product).merge(@product_scope)
        @product_content = authorized_product_contents.joins(:content).merge(content).first
        @product = @product_content&.product
      end
      throw_resource_not_found(name: 'repository set', id: params[:id]) if @product_content.nil?
    end

    def find_product
      if params[:product_id]
        @product = @product_scope.find_by(id: params[:product_id])
        throw_resource_not_found(name: 'product', id: params[:product_id]) if @product.nil?
      end
    end

    def set_readable_product_scope
      @product_scope = Katello::Product.readable
    end

    def set_editable_product_scope
      @product_scope = Katello::Product.editable
    end

    def find_organization
      @organization = @product&.organization || @consumable&.organization || super
    end

    def custom_product?
      fail _('Repository sets are not available for custom products.') if @product&.custom?
    end

    def substitutions
      params.permit(:basearch, :releasever, :repository_id).to_h
    end

    def find_authorized_activation_key
      return unless params[:activation_key_id]
      @activation_key = ActivationKey.readable.find_by(:id => params[:activation_key_id])
      @consumable = @activation_key
      throw_resource_not_found(name: 'activation_key', id: params[:activation_key_id]) if @activation_key.blank?
    end

    def find_authorized_host
      return unless params[:host_id]
      find_host_with_subscriptions(params[:host_id], :view_hosts)
      @consumable = @host.subscription_facet
    end

    def setup_params
      return unless params[:id]
      params[:content_access_mode_all] = true
      if params[:entity] == :activation_key
        params[:activation_key_id] ||= params[:id]
      else
        params[:host_id] ||= params[:id]
      end
    end

    def check_airgapped
      if @organization.cdn_configuration.export_sync?
        fail HttpErrors::BadRequest, _("Repositories are not available for enablement while CDN configuration is set to Air-gapped (disconnected).")
      end
    end

    def sort_score(pc) # sort order for enabled
      if pc.enabled_content_override&.value == "1"
        4 # overridden to enabled
      elsif pc.enabled_content_override.nil? && pc.enabled
        3 # enabled
      elsif pc.enabled_content_override.nil? && !pc.enabled
        2 # disabled
      elsif pc.enabled_content_override&.value == "0"
        1 # overridden to disabled
      else
        0
      end
    end

    def custom_sort_results(unsorted_relation)
      return unsorted_relation unless params[:sort_by] == 'enabled_by_default'
      product_content_finder = ProductContentFinder.wrap_with_overrides(
        product_contents: unsorted_relation,
        overrides: @consumable&.content_overrides,
        status: params[:status])
      sorted_pcps = if params[:sort_by] == 'enabled_by_default' && params[:sort_order] == 'desc'
                      product_content_finder.sort { |pca, pcb| sort_score(pca) <=> sort_score(pcb) }.reverse!
                    elsif params[:sort_by] == 'enabled_by_default'
                      product_content_finder.sort { |pca, pcb| sort_score(pca) <=> sort_score(pcb) }
                    else
                      product_content_finder
                    end
      sort_order = sorted_pcps.map(&:id)
      unsorted_relation.reorder(Arel.sql("array_position('{#{sort_order.join(',')}}'::int[], #{Katello::ProductContent.table_name}.id)"))
    end
  end
end
