module Katello
  module Resources
    module Candlepin
      TOTAL_COUNT_HEADER = :x_total_count # as parsed by rest_client

      class CandlepinResource < HttpResource
        cfg = SETTINGS[:katello][:candlepin]
        url = cfg[:url]
        uri = URI.parse(url)
        self.prefix = uri.path
        self.site = "#{uri.scheme}://#{uri.host}:#{uri.port}"
        self.consumer_secret = cfg[:oauth_secret]
        self.consumer_key = cfg[:oauth_key]
        self.ssl_ca_file = ::Cert::Certs.backend_ca_cert_file(:candlepin)

        class << self
          def process_response(response)
            debug_level = response.code >= 400 ? :error : :debug
            logger.send(debug_level, "Candlepin request #{response.headers[:x_candlepin_request_uuid]} returned with code #{response.code}")
            super
          end

          def raise_rest_client_exception(error, path, http_method)
            # this differentiates between Tomcat returning a 404 (candlepin is down or not deployed)
            # vs a 404 from Candlepin itself
            unless error&.response&.headers&.dig(:x_version)
              fail ::Katello::Errors::CandlepinNotRunning
            end

            super
          end
        end

        def self.logger
          ::Foreman::Logging.logger('katello/cp_rest')
        end

        def self.default_headers(uuid = nil)
          # There are cases where virt-who needs to act on behalf of hypervisors it is managing.
          # If the uuid is specified, then that consumer is used in the headers rather than the
          # virt-who consumer uuid.
          # Current example is creating a hypervisor that in turn needs to get compliance.
          if !uuid.nil? && User.consumer?
            cp_oauth_header = { 'cp-consumer' => uuid }
          else
            cp_oauth_header = User.cp_oauth_header
          end

          headers = {'accept' => 'application/json',
                     'accept-language' => I18n.locale,
                     'content-type' => 'application/json'}

          request_id = ::Logging.mdc['request']
          headers['X-Correlation-ID'] = request_id if request_id

          headers.merge!(cp_oauth_header)
        end

        def self.name_to_key(a_name)
          a_name.tr(' ', '_')
        end

        def self.included_list(included)
          included.map { |value| "include=#{value}" }.join('&')
        end

        def self.fetch_paged(page_size = -1)
          if page_size == -1
            page_size = SETTINGS[:katello][:candlepin][:bulk_load_size]
          end
          page = 0
          content = []
          loop do
            page += 1
            data = yield("per_page=#{page_size}&page=#{page}")
            content.concat(data)
            break if data.size < page_size
          end
          content
        end
      end

      class UpstreamCandlepinResource < CandlepinResource
        extend ::Katello::Util::HttpProxy

        self.prefix = '/subscription'

        class << self
          delegate :[], to: :json_resource

          def resource(options = {}, url: self.site + self.path, client_cert: self.client_cert, client_key: self.client_key, ca_file: nil)
            cert_store = OpenSSL::X509::Store.new
            cert_store.add_file(ca_file) if ca_file

            if proxy&.cacert&.present?
              Foreman::Util.add_ca_bundle_to_store(proxy.cacert, cert_store)
            end
            RestClient::Resource.new(url,
                                     :ssl_client_cert => OpenSSL::X509::Certificate.new(client_cert),
                                     :ssl_client_key => OpenSSL::PKey::RSA.new(client_key),
                                     :ssl_cert_store => cert_store,
                                     :verify_ssl => ca_file ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE,
                                     :open_timeout => Setting[:manifest_refresh_timeout],
                                     :timeout => Setting[:manifest_refresh_timeout],
                                     :proxy => self.proxy_uri,
                                     **options
                                    )
          end

          def json_resource(options = {}, url: self.site + self.path, client_cert: self.client_cert, client_key: self.client_key, ca_file: nil)
            options.deep_merge!(headers: self.default_headers)
            resource(options, url: url, client_cert: client_cert, client_key: client_key, ca_file: ca_file)
          end

          def rest_client(_http_type = nil, method = :get, path = self.path)
            # No oauth upstream
            self.consumer_secret = nil
            self.consumer_key = nil

            resource({ http_method: method }, url: self.site + path, client_cert: client_cert, client_key: client_key, ca_file: nil)
          end

          def client_cert
            upstream_id_cert['cert']
          end

          def client_key
            upstream_id_cert['key']
          end

          def upstream_api_uri
            URI.parse(upstream_consumer['apiUrl'])
          end

          def site
            "#{upstream_api_uri.scheme}://#{upstream_api_uri.host}"
          end

          def upstream_id_cert
            unless upstream_consumer && upstream_consumer['idCert'] && upstream_consumer['idCert']['cert'] && upstream_consumer['idCert']['key']
              Rails.logger.error "Upstream identity certificate not available"
              fail _("Upstream identity certificate not available")
            end
            upstream_consumer['idCert']
          end

          def upstream_owner_id
            JSON.parse(Katello::Resources::Candlepin::UpstreamConsumer.resource.get.body)['owner']['key']
          rescue RestClient::Exception => e
            Rails.logger.error "Unable to find upstream owner for consumer"
            raise e
          end

          def upstream_consumer_id
            upstream_consumer['uuid']
          end

          def upstream_consumer
            fail _("Current organization not set.") unless Organization.current
            upstream_consumer = Organization.current.owner_details['upstreamConsumer']
            fail Katello::Errors::NoManifestImported unless upstream_consumer

            upstream_consumer
          end
        end # class << self
      end # UpstreamCandlepinResource

      module AdminResource
        def path
          "#{self.prefix}/admin"
        end
      end

      module ConsumerResource
        def path(id = nil)
          "#{self.prefix}/consumers/#{id}"
        end
      end

      module OwnerResource
        def path(id = nil)
          "#{self.prefix}/owners/#{id}"
        end
      end

      module PoolResource
        def path(id = nil, owner_label = nil)
          if owner_label && id
            "#{prefix}/owners/#{owner_label}/pools/#{id}"
          elsif owner_label
            "#{prefix}/owners/#{owner_label}/pools/"
          else
            "#{prefix}/pools/#{id}"
          end
        end
      end
    end
  end
end
