module VCAP::CloudController
  class ServicePlansController < RestController::ModelController
    define_attributes do
      attribute :name,              String
      attribute :free,              Message::Boolean
      attribute :description,       String
      attribute :extra,             String,           default: nil
      attribute :unique_id,         String,           default: nil
      to_one :service
      to_many :service_instances
      attribute :public, Message::Boolean, default: true
    end

    query_parameters :active, :service_guid, :service_instance_guid, :service_broker_guid, :unique_id
    # added :service_broker_guid here for readability, it is actually implemented as a search filter
    # in the #get_filtered_dataset_for_enumeration method because ModelControl does not support
    # searching on parameters that are not directly associated with the model

    allow_unauthenticated_access only: :enumerate

    def enumerate
      if SecurityContext.missing_token?
        raise CloudController::Errors::NotAuthenticated if VCAP::CloudController::FeatureFlag.enabled?(:hide_marketplace_from_unauthenticated_users)

        single_filter = @opts[:q][0] if @opts[:q]
        service_guid = single_filter.split(':')[1] if single_filter && single_filter.start_with?('service_guid')

        plans = ServicePlan.where(active: true, public: true)
        if service_guid.present?
          services = Service.where(guid: service_guid)
          plans = plans.where(service_id: services.select(:id))
        end

        @opts.delete(:inline_relations_depth)
        collection_renderer.render_json(
          self.class,
          plans,
          self.class.path,
          @opts,
          {}
        )
      elsif SecurityContext.invalid_token?
        raise CloudController::Errors::InvalidAuthToken
      else
        super
      end
    end

    # # Read operation
    # #
    # # @param [String] guid The GUID of the object to read.
    # This method is needed so the service plan is read during rendering
    # with the same service-instance-based privilege escalation that
    # is used when access to the object is validated.
    def read(guid)
      obj = find_guid(guid)
      validate_access(:read, obj)
      object_renderer.render_json_with_read_privileges(self.class, obj, @opts)
    end

    def create
      404
    end

    def update(guid)
      json_msg = self.class::UpdateMessage.decode(body)
      @request_attrs = json_msg.extract(stringify_keys: true).select { |key, _value| key == 'public' }
      logger.debug 'cc.update', guid: guid, attributes: redact_attributes(:update, request_attrs)
      raise InvalidRequest unless request_attrs

      obj = find_guid(guid)

      before_update(obj)

      model.db.transaction do
        obj.lock!
        validate_access(:read_for_update, obj, request_attrs)
        obj.update_from_hash(request_attrs)
        validate_access(:update, obj, request_attrs)
      end

      after_update(obj)

      [HTTP::CREATED, object_renderer.render_json(self.class, obj, @opts)]
    end

    def delete(guid)
      plan = find_guid_and_validate_access(:delete, guid)

      raise CloudController::Errors::ApiError.new_from_details('AssociationNotEmpty', 'service_instances', plan.class.table_name) if plan.service_instances.present?

      plan.destroy

      [HTTP::NO_CONTENT, nil]
    end

    def self.translate_validation_exception(e, attributes)
      name_errors = e.errors.on(%i[service_id name])
      if name_errors && name_errors.include?(:unique)
        CloudController::Errors::ApiError.new_from_details('ServicePlanNameTaken', "#{attributes['service_id']}-#{attributes['name']}")
      else
        CloudController::Errors::ApiError.new_from_details('ServicePlanInvalid', e.errors.full_messages)
      end
    end

    def get_filtered_dataset_for_enumeration(model, dataset, query_params, opts)
      single_filter = opts[:q][0] if opts[:q]

      if single_filter && single_filter.start_with?('service_broker_guid')
        service_broker_guid = single_filter.split(/(:| IN )/)[2]

        Query.
          filtered_dataset_from_query_params(model, dataset, query_params, { q: '' }).
          select_all(:service_plans).
          left_join(:services, id: :service_plans__service_id).
          left_join(:service_brokers, id: :services__service_broker_id).
          where(service_brokers__guid: service_broker_guid)
      else
        super
      end
    end

    define_messages
    define_routes
  end
end
