# rubocop:disable Metrics/ClassLength
require "pulpcore_client"
module Katello
  module Pulp3
    class Repository
      include Katello::Util::HttpProxy
      attr_accessor :repo
      attr_accessor :smart_proxy
      delegate :root, to: :repo
      delegate :pulp3_api, to: :smart_proxy

      COPY_UNIT_PAGE_SIZE = 10_000

      def initialize(repo, smart_proxy)
        @repo = repo
        @smart_proxy = smart_proxy
      end

      def partial_repo_path
        fail NotImplementedError
      end

      def with_mirror_adapter
        if smart_proxy.pulp_primary?
          return self
        else
          return RepositoryMirror.new(self)
        end
      end

      def self.api(smart_proxy)
        api_class = RepositoryTypeManager.find_by(:pulp3_service_class, self).pulp3_api_class
        api_class ? api_class.new(smart_proxy) : Katello::Pulp3::Api::Core.new(smart_proxy)
      end

      def core_api
        Katello::Pulp3::Api::Core.new(smart_proxy)
      end

      def api
        @api ||= self.class.api(smart_proxy)
      end

      def ignore_404_exception(*)
        yield
      rescue api.class.api_exception_class => e
        raise e unless e.code == 404
        nil
      end

      def skip_types
        nil
      end

      def content_service
        Katello::Pulp3::Content
      end

      def create_remote
        remote_file_data = api.class.remote_class.new(remote_options)
        response = api.remotes_api.create(remote_file_data)
        repo.update!(:remote_href => response.pulp_href)
      end

      def update_remote
        href = repo.remote_href
        if remote_options[:url].blank?
          if href
            repo.update(remote_href: nil)
            delete_remote href
          end
        else
          if href
            remote_partial_update
          else
            create_remote
          end
        end
      end

      def remote_partial_update
        api.remotes_api.partial_update(repo.remote_href, remote_options)
      end

      def delete_remote(href = repo.remote_href)
        ignore_404_exception { api.remotes_api.delete(href) } if href
      end

      def self.instance_for_type(repo, smart_proxy)
        Katello::RepositoryTypeManager.repository_types[repo.root.content_type].pulp3_service_class.new(repo, smart_proxy)
      end

      def should_purge_empty_contents?
        false
      end

      def generate_backend_object_name
        "#{root.label}-#{repo.id}#{rand(9999)}"
      end

      def repository_reference
        RepositoryReference.find_by(:root_repository_id => repo.root_id, :content_view_id => repo.content_view.id)
      end

      def distribution_reference
        DistributionReference.find_by(:repository_id => repo.id)
      end

      def create_mirror_entities
        RepositoryMirror.new(self).create_entities
      end

      def refresh_mirror_entities
        RepositoryMirror.new(self).refresh_entities
      end

      def mirror_needs_updates?
        RepositoryMirror.new(self).needs_updates?
      end

      def refresh_if_needed
        tasks = []
        tasks << update_remote if remote_needs_updates?
        tasks << update_distribution if distribution_needs_update?
        tasks.compact
      end

      def get_remote(href = repo.remote_href)
        api.remotes_api.read(href)
      end

      def remote_needs_updates?
        if repo.remote_href
          remote = get_remote
          computed = compute_remote_options
          computed.keys.any? { |key| remote.send(key) != computed[key] }
        elsif repo.url
          true
        else
          false
        end
      end

      def get_distribution(href = distribution_reference.href)
        api.get_distribution(href)
      end

      def distribution_needs_update?
        if distribution_reference
          expected = secure_distribution_options(relative_path).except(:name).compact
          actual = get_distribution.to_hash
          expected != actual.slice(*expected.keys)
        elsif repo.environment
          true
        else
          false
        end
      end

      def compute_remote_options(computed_options = remote_options)
        computed_options.except(:name, :client_key)
      end

      def create
        unless repository_reference
          response = api.repositories_api.create(
            name: generate_backend_object_name)
          RepositoryReference.create!(
           root_repository_id: repo.root_id,
           content_view_id: repo.content_view.id,
           repository_href: response.pulp_href)
          response
        end
      end

      def update
        api.repositories_api.update(repository_reference.try(:repository_href), name: generate_backend_object_name)
      end

      def list(options)
        api.repositories_api.list(options).results
      end

      def read
        api.repositories_api.read(repository_reference.try(:repository_href))
      end

      def delete_repository(repo_reference = repository_reference)
        href = repo_reference.try(:repository_href)
        repo_reference.try(:destroy)
        ignore_404_exception { api.repositories_api.delete(href) } if href
      end

      def sync(options = {})
        repository_sync_url_data = api.class.repository_sync_url_class.new(sync_url_params(options))
        [api.repositories_api.sync(repository_reference.repository_href, repository_sync_url_data)]
      end

      def sync_url_params(_sync_options)
        params = {remote: repo.remote_href, mirror: repo.root.mirror_on_sync}
        params[:skip_types] = skip_types if skip_types
        params
      end

      def create_publication
        publication_data = api.class.publication_class.new(publication_options(repo.version_href))
        api.publications_api.create(publication_data)
      end

      def publication_options(repository_version)
        {
          repository_version: repository_version
        }
      end

      def relative_path
        repo.relative_path.sub(/^\//, '')
      end

      def refresh_distributions
        if repo.docker?
          dist = lookup_distributions(base_path: repo.container_repository_name).first
        else
          dist = lookup_distributions(base_path: repo.relative_path).first
        end

        # First check if the distribution exists
        if dist
          dist_ref = distribution_reference
          # If we have a DistributionReference, update the distribution
          if dist_ref
            return update_distribution
          # If no DistributionReference, create a DistributionReference and return
          else
            save_distribution_references([dist.pulp_href])
            return []
          end
        end

        # So far, it looks like there is no distribution. Try to create one.
        begin
          create_distribution(relative_path)
        rescue api.class.client_module::ApiError => e
          # Now it seems there is a distribution. Fetch it and save the reference.
          if e.message.include?("\"base_path\":[\"This field must be unique.\"]") ||
              e.message.include?("\"base_path\":[\"Overlaps with existing distribution\"")
            dist = lookup_distributions(base_path: repo.relative_path).first
            save_distribution_references([dist.pulp_href])
            return []
          else
            raise e
          end
        end
      end

      def create_distribution(path)
        distribution_data = api.class.distribution_class.new(secure_distribution_options(path))
        api.distributions_api.create(distribution_data)
      end

      def lookup_distributions(args)
        api.distributions_api.list(args).results
      end

      def update_distribution
        if distribution_reference
          options = secure_distribution_options(relative_path).except(:name)
          distribution_reference.update(:content_guard_href => options[:content_guard])
          api.distributions_api.partial_update(distribution_reference.href, options)
        end
      end

      def copy_units_by_href(unit_hrefs)
        tasks = []
        unit_hrefs.each_slice(COPY_UNIT_PAGE_SIZE) do |slice|
          tasks << create_version(:add_content_units => slice)
        end
        tasks
      end

      def copy_version(from_repository)
        create_version(:base_version => from_repository.version_href)
      end

      def version_zero?
        repo.version_href.ends_with?('/versions/0/')
      end

      def delete_version
        ignore_404_exception { api.repository_versions_api.delete(repo.version_href) } unless version_zero?
      end

      def create_version(options = {})
        api.repositories_api.modify(repository_reference.repository_href, options)
      end

      def save_distribution_references(hrefs)
        hrefs.each do |href|
          pulp3_distribution_data = api.get_distribution(href)
          path, content_guard_href = pulp3_distribution_data&.base_path, pulp3_distribution_data&.content_guard
          unless distribution_reference
            # Ensure that duplicates won't be created in the case of a race condition
            DistributionReference.where(path: path, href: href, repository_id: repo.id, content_guard_href: content_guard_href).first_or_create!
          end
        end
      end

      def delete_distributions
        if (dist_ref = distribution_reference)
          ignore_404_exception { api.delete_distribution(dist_ref.href) }
          dist_ref.destroy!
        end
      end

      def delete_distributions_by_path
        path = relative_path
        dists = lookup_distributions(base_path: path)

        task = api.delete_distribution(dists.first.pulp_href) if dists.first
        Katello::Pulp3::DistributionReference.where(:path => path).destroy_all
        task
      end

      def common_remote_options
        remote_options = {
          tls_validation: root.verify_ssl_on_sync,
          name: generate_backend_object_name,
          url: root.url,
          proxy_url: root.http_proxy&.full_url
        }
        remote_options[:url] = root.url unless root.url.blank?
        remote_options[:download_concurrency] = root.download_concurrency unless root.download_concurrency.blank?
        if !root.upstream_username.blank? && !root.upstream_password.blank?
          remote_options.merge!(username: root.upstream_username,
                               password: root.upstream_password)
        end
        remote_options.merge!(ssl_remote_options)
      end

      def secure_distribution_options(path)
        secured_distribution_options = {}
        if root.unprotected
          secured_distribution_options[:content_guard] = nil
        else
          secured_distribution_options[:content_guard] = ::Katello::Pulp3::ContentGuard.first.pulp_href
        end
        secured_distribution_options.merge!(distribution_options(path))
      end

      def ssl_remote_options
        if root.redhat? && Katello::Resources::CDN::CdnResource.redhat_cdn?(root.url)
          {
            client_cert: root.product.certificate,
            client_key: root.product.key,
            ca_cert: Katello::Repository.feed_ca_cert(root.url)
          }
        elsif root.custom?
          {
            client_cert: root.ssl_client_cert&.content,
            client_key: root.ssl_client_key&.content,
            ca_cert: root.ssl_ca_cert&.content
          }
        else
          {}
        end
      end

      def lookup_version(href)
        api.repository_versions_api.read(href) if href
      rescue api.class.api_exception_class => e
        Rails.logger.error "Exception when calling repository_versions_api->read: #{e}"
        nil
      end

      def lookup_publication(href)
        api.publications_api.read(href) if href
      rescue api.class.api_exception_class => e
        Rails.logger.error "Exception when calling publications_api->read: #{e}"
        nil
      end

      def remove_content(content_units)
        if repo.root.content_type == "docker"
          api.repositories_api.remove(repository_reference.repository_href, content_units: content_units.map(&:pulp_id))
        else
          api.repositories_api.modify(repository_reference.repository_href, remove_content_units: content_units.map(&:pulp_id))
        end
      end

      def add_content(content_unit_href)
        content_unit_href = [content_unit_href] unless content_unit_href.is_a?(Array)
        api.repositories_api.modify(repository_reference.repository_href, add_content_units: content_unit_href)
      end

      def add_content_for_repo(repository_href, content_unit_href)
        content_unit_href = [content_unit_href] unless content_unit_href.is_a?(Array)
        api.repositories_api.modify(repository_href, add_content_units: content_unit_href)
      end

      def unit_keys(uploads)
        uploads.map do |upload|
          upload.except('id')
        end
      end
    end
  end
end