module Katello
  class AlternateContentSource < Katello::Model
    audited

    include Ext::LabelFromName
    include Encryptable
    include ::ScopedSearchExtensions
    include Authorization::AlternateContentSource
    include ForemanTasks::Concerns::ActionSubject

    self.table_name = :katello_alternate_content_sources

    ACS_TYPES = %w(custom simplified rhui).freeze
    CONTENT_TYPES = [::Katello::Repository::YUM_TYPE, ::Katello::Repository::FILE_TYPE].freeze
    AUDIT_REFRESH_ACTION = 'refresh'.freeze

    encrypts :upstream_password

    belongs_to :ssl_ca_cert, inverse_of: :ssl_ca_alternate_content_sources, class_name: "Katello::ContentCredential"
    belongs_to :ssl_client_cert, inverse_of: :ssl_client_alternate_content_sources, class_name: "Katello::ContentCredential"
    belongs_to :ssl_client_key, inverse_of: :ssl_key_alternate_content_sources, class_name: "Katello::ContentCredential"

    validate :validate_ssl_ids
    validate :validate_products

    has_many :alternate_content_source_products, dependent: :delete_all, inverse_of: :alternate_content_source,
             class_name: "Katello::AlternateContentSourceProduct"
    has_many :products, through: :alternate_content_source_products, inverse_of: :alternate_content_sources,
             class_name: "Katello::Product"

    has_many :smart_proxy_alternate_content_sources, dependent: :delete_all,
             inverse_of: :alternate_content_source
    has_many :smart_proxies, -> { distinct }, through: :smart_proxy_alternate_content_sources

    validates :base_url, :subpaths, :upstream_username,
              :upstream_password, if: :simplified?, absence: true
    validates :base_url, if: -> { custom? || rhui? }, presence: true
    validates :label, :uniqueness => true
    validates :name, :uniqueness => true, presence: true
    # verify ssl must be validated this way due to presence: <bool> failing on a value of false
    validates :verify_ssl, if: -> { custom? || rhui? }, inclusion: {
      in: [true, false],
      message: "must be provided for custom or rhui ACS",
    }
    validates :verify_ssl, if: :simplified?, inclusion: {
      in: [nil],
      message: "cannot be provided for simplified ACS",
    }
    validates :alternate_content_source_type, inclusion: {
      in: ->(_) { ACS_TYPES },
      allow_blank: false,
      message: ->(_, _) { _("is not a valid type. Must be one of the following: %s") % ACS_TYPES.join(',') },
    }
    validates :content_type, inclusion: {
      in: ->(_) { RepositoryTypeManager.defined_repository_types.keys & CONTENT_TYPES },
      allow_blank: false,
      message: ->(_, _) { _("is not allowed for ACS. Must be one of the following: %s") % (RepositoryTypeManager.defined_repository_types.keys & CONTENT_TYPES).sort.join(',') },
    }
    validates :content_type, if: -> { rhui? }, inclusion: {
      in: [::Katello::Repository::YUM_TYPE],
      message: "'%{value}' is not valid for RHUI ACS",
    }
    validate :constraint_acs_update, on: :update
    validates_with Validators::AlternateContentSourcePathValidator, :attributes => [:base_url, :subpaths], :if => :custom?

    scope :uses_http_proxies, -> { where(use_http_proxies: true) }

    scoped_search on: :name, complete_value: true
    scoped_search on: :label, complete_value: true
    scoped_search on: :description, complete_value: true
    scoped_search on: :base_url, complete_value: true
    scoped_search on: :subpath, ext_method: :search_by_subpath
    scoped_search on: :content_type, complete_value: true
    scoped_search on: :alternate_content_source_type, complete_value: true
    scoped_search on: :upstream_username, complete_value: true
    scoped_search on: :id, relation: :smart_proxies, rename: :smart_proxy_id, validator: ScopedSearch::Validators::INTEGER, only_explicit: true
    scoped_search on: :name, relation: :smart_proxies, rename: :smart_proxy_name, complete_value: true
    scoped_search on: :id, relation: :products, rename: :product_id, validator: ScopedSearch::Validators::INTEGER, only_explicit: true
    scoped_search on: :name, relation: :products, rename: :product_name, complete_value: true

    def backend_service(smart_proxy, repository = nil)
      @service ||= ::Katello::Pulp3::AlternateContentSource.new(self, smart_proxy, repository)
    end

    def custom?
      alternate_content_source_type == 'custom'
    end

    def simplified?
      alternate_content_source_type == 'simplified'
    end

    def rhui?
      alternate_content_source_type == 'rhui'
    end

    def self.with_products(products)
      products = [products] unless products.is_a?(Array)
      joins(:alternate_content_source_products).where('katello_alternate_content_source_products.product_id in (:product_ids)', product_ids: products.pluck(:id))
    end

    def self.with_type(content_type)
      where(content_type: content_type)
    end

    def self.search_by_subpath(_key, operator, value)
      conditions = sanitize_sql_for_conditions(["? #{operator} ANY (subpaths)", value_to_sql(operator, value)])
      { conditions: conditions }
    end

    def latest_dynflow_refresh_task
      @latest_dynflow_refresh_task ||= ForemanTasks::Task::DynflowTask.where(:label => Actions::Katello::AlternateContentSource::Refresh.name).
          for_resource(self).order(:started_at).last
    end

    def audit_refresh
      write_audit(action: AUDIT_REFRESH_ACTION, comment: _('Successfully refreshed.'), audited_changes: {})
    end

    def audit_updated_products(old_product_ids)
      write_audit(action: 'update', comment: _('Products updated.'), audited_changes: { 'product_ids' => [old_product_ids, product_ids] })
    end

    def self.humanize_class_name(_name = nil)
      "Alternate Content Sources"
    end

    # Disallow static properties from being modified on update
    def constraint_acs_update
      if changes.keys.include? "content_type"
        errors.add(:content_type, "cannot be modified once an ACS is created")
      end
      if changes.keys.include? "alternate_content_source_type"
        errors.add(:alternate_content_source_type, "cannot be modified once an ACS is created")
      end
    end

    # Validate ssl-* ids which require complex/custom error messages
    def validate_ssl_ids
      # Simplified ACS's should never have ssl-* params populated
      if simplified?
        if changes.keys.include? "ssl_ca_cert_id"
          errors.add(:ssl_ca_cert, "cannot be set for simplified ACS")
        end
        if changes.keys.include? "ssl_client_cert_id"
          errors.add(:ssl_client_cert, "cannot be set for simplified ACS")
        end
        if changes.keys.include? "ssl_client_key_id"
          errors.add(:ssl_client_key, "cannot be set for simplified ACS")
        end

      # Custom and RHUI ACS's should have valid keys where populated
      else
        if ssl_ca_cert_id.present? && ssl_ca_cert.nil?
          errors.add(:ssl_ca_cert, "with ID '#{ssl_ca_cert_id}' couldn't be found")
        end
        if ssl_client_cert_id.present? && ssl_client_cert.nil?
          errors.add(:ssl_client_cert, "with ID '#{ssl_client_cert_id}' couldn't be found")
        end
        if ssl_client_key_id.present? && ssl_client_key.nil?
          errors.add(:ssl_client_key, "with ID '#{ssl_client_key_id}' couldn't be found")
        end
      end
    end

    def validate_products
      if (custom? || rhui?) && products.present?
        errors.add(:product_ids, "cannot be set for custom or rhui ACS")
      end
    end
  end
end
