module Katello
  module Glue::Candlepin::Product
    def self.included(base)
      base.send :include, LazyAccessor
      base.send :include, InstanceMethods

      base.class_eval do
        lazy_accessor :productContent, :multiplier, :href, :attrs,
          :initializer => lambda { |_s| convert_from_cp_fields(Resources::Candlepin::Product.get(cp_id)[0]) }
        # Entitlement Certificate for this product
        lazy_accessor :certificate,
          :initializer => lambda { |_s| Resources::Candlepin::Product.certificate(cp_id, self.organization.label) },
          :unless => lambda { |_s| cp_id.nil? }
        # Entitlement Key for this product
        lazy_accessor :key, :initializer => lambda { |_s| Resources::Candlepin::Product.key(cp_id, self.organization.label) }, :unless => lambda { |_s| cp_id.nil? }

        # we must store custom logger object during product importing so we can log status
        # from various places like callbacks
        attr_accessor :import_logger
      end
    end

    def self.validate_name(name)
      name.gsub(/[^a-z0-9\-_ ]/i, "")
    end

    def self.import_from_cp(attrs = nil, &block)
      product_content_attrs = attrs.delete(:productContent) || []
      import_logger = attrs[:import_logger]

      attrs = attrs.merge('name' => validate_name(attrs['name']), 'label' => Util::Model.labelize(attrs['name']))

      product = Product.new(attrs, &block)
      product.orchestration_for = :import_from_cp_ar_setup
      product.save!
      product.productContent_will_change!
      product.productContent = product.build_product_content(product_content_attrs)
      product.orchestration_for = :import_from_cp
      product.save!

    rescue => e
      [Rails.logger, import_logger].each do |logger|
        logger.error "Failed to create product #{attrs['name']}: #{e}" if logger
      end
      raise e
    end

    module InstanceMethods
      def initialize(attribs = nil, options = {})
        unless attribs.nil?
          attributes_key = attribs.key?(:attributes) ? :attributes : 'attributes'
          if attribs.key?(attributes_key)
            attribs[:attrs] = attribs[attributes_key]
            attribs.delete(attributes_key)
          end

          @productContent = [] unless attribs.key?(:productContent)

          # ugh. hack-ish. otherwise we have to modify code every time things change on cp side
          attribs = attribs.reject do |k, _v|
            !self.class.column_defaults.keys.member?(k.to_s) && (!respond_to?(:"#{k.to_s}=") rescue true)
          end
        end

        super
      end

      def displayable_product_contents
        self.productContent.select(&:displayable?)
      end

      def orphaned?
        self.provider.redhat_provider? && self.certificate.nil?
      end

      def build_product_content(attrs)
        @productContent = attrs.collect { |pc| Katello::Candlepin::ProductContent.new pc }
      end

      def support_level
        return _attr(:support_level)
      end

      def arch
        attrs.each do |attr|
          if attr[:name] == 'arch'
            return "noarch" if attr[:value] == 'ALL'
            return attr[:value]
          end
        end
        default_arch
      end

      def _attr(key)
        puts "Looking for: #{key}"
        attrs.each do |attr|
          puts "attr: name: #{attr[:name]} value: #{attr[:value]}"
          if attr[:name] == key.to_s
            return attr[:value]
          end
        end
        nil
      end

      def default_arch
        "noarch"
      end

      def convert_from_cp_fields(cp_json)
        ar_safe_json = cp_json.key?(:attributes) ? cp_json.merge(:attrs => cp_json.delete(:attributes)) : cp_json
        ar_safe_json[:productContent] = ar_safe_json[:productContent].collect { |pc| ::Katello::Candlepin::ProductContent.new(pc, self.id) }
        ar_safe_json[:attrs] = remove_hibernate_fields(cp_json[:attrs]) if ar_safe_json.key?(:attrs)
        ar_safe_json[:attrs] ||= []
        ar_safe_json.except('id')
      end

      # Candlepin sends back its internal hibernate fields in the json. However it does not accept them in return
      # when updating (PUT) objects.
      def remove_hibernate_fields(elements)
        return nil unless elements
        elements.collect { |e| e.except(:id, :created, :updated) }
      end

      def add_content(content)
        Resources::Candlepin::Product.add_content self.cp_id, content.content.id, true
        self.productContent << content
      end

      def remove_content_by_id(content_id)
        Resources::Candlepin::Product.remove_content cp_id, content_id
      end

      def product_content_by_id(content_id)
        self.productContent.find { |pc| pc.content.id == content_id }
      end

      def product_content_by_name(content_name)
        self.productContent.find { |pc| pc.content.name == content_name }
      end

      def import_subscription(subscription_id)
        sub = ::Katello::Subscription.where(:cp_id => subscription_id).first_or_create
        sub.import_data
        pools = ::Katello::Resources::Candlepin::Product.pools(self.organization.label, self.cp_id)
        pools.each do |pool_json|
          pool = ::Katello::Pool.where(:cp_id => pool_json['id']).first_or_create
          pool.import_data
        end
      end

      protected

      def added_content
        old_content_ids = productContent_change[0].nil? ? [] : productContent_change[0].map { |pc| pc.content.label }
        new_content_ids = productContent_change[1].map { |pc| pc.content.label }

        added_content_ids = new_content_ids - old_content_ids

        added_content = productContent_change[1].select { |pc| added_content_ids.include?(pc.content.label) }
        added_content
      end

      def deleted_content
        old_content_ids = productContent_change[0].nil? ? [] : productContent_change[0].map { |pc| pc.content.label }
        new_content_ids = productContent_change[1].map { |pc| pc.content.label }

        deleted_content_ids = old_content_ids - new_content_ids

        deleted_content = productContent_change[0].select { |pc| deleted_content_ids.include?(pc.content.label) }
        deleted_content
      end
    end
  end
end