module VCAP::CloudController
  class FeatureFlag < Sequel::Model
    FF_ERROR_MESSAGE_REGEX = /\A[[:alnum:][:punct:][:print:]]+\Z/

    class UndefinedFeatureFlagError < StandardError
    end

    DEFAULT_FLAGS = {
      user_org_creation: false,
      private_domain_creation: true,
      app_bits_upload: true,
      app_scaling: true,
      route_creation: true,
      service_instance_creation: true,
      diego_docker: false,
      diego_cnb: false,
      set_roles_by_username: true,
      unset_roles_by_username: true,
      task_creation: true,
      env_var_visibility: true,
      space_scoped_private_broker_creation: true,
      space_developer_env_var_visibility: true,
      service_instance_sharing: false,
      hide_marketplace_from_unauthenticated_users: false,
      resource_matching: true,
      route_sharing: false
    }.freeze

    ADMIN_SKIPPABLE = %i[
      app_bits_upload
      app_scaling
      set_roles_by_username
      space_developer_env_var_visibility
      task_creation
      unset_roles_by_username
    ].freeze

    ADMIN_READ_ONLY_SKIPPABLE = [:space_developer_env_var_visibility].freeze

    @feature_flag_overrides = nil

    export_attributes :name, :enabled, :error_message
    import_attributes :name, :enabled, :error_message

    def validate
      validates_presence :name
      validates_unique :name
      validates_presence :enabled

      validates_includes DEFAULT_FLAGS.keys.map(&:to_s), :name
      validates_format FF_ERROR_MESSAGE_REGEX, :error_message if error_message
    end

    def self.enabled?(feature_flag_name, raise_unless_enabled: false)
      return true if ADMIN_SKIPPABLE.include?(feature_flag_name) && admin?
      return true if ADMIN_READ_ONLY_SKIPPABLE.include?(feature_flag_name) && admin_read_only?

      feature_flag = FeatureFlag.find(name: feature_flag_name.to_s)
      enabled = if feature_flag
                  feature_flag.enabled
                else
                  DEFAULT_FLAGS.fetch(feature_flag_name)
                end

      if raise_unless_enabled && !enabled
        err_message = feature_flag&.error_message ? feature_flag.error_message : feature_flag_name
        raise CloudController::Errors::ApiError.new_from_details('FeatureDisabled', err_message)
      end

      enabled
    rescue KeyError
      raise UndefinedFeatureFlagError.new "invalid key: #{feature_flag_name}"
    end

    def self.disabled?(feature_flag_name)
      !enabled?(feature_flag_name)
    end

    def self.raise_unless_enabled!(feature_flag_name)
      enabled?(feature_flag_name, raise_unless_enabled: true)
    end

    def self.admin?
      VCAP::CloudController::SecurityContext.admin?
    end

    def self.admin_read_only?
      VCAP::CloudController::SecurityContext.admin_read_only?
    end

    def self.override_default_flags(feature_flag_overrides)
      invalid_keys = feature_flag_overrides.keys.to_set - FeatureFlag::DEFAULT_FLAGS.keys.to_set
      raise "Invalid feature flag name(s): #{invalid_keys.to_a}" if invalid_keys.any?

      invalid_values = feature_flag_overrides.reject { |_, v| v.is_a?(TrueClass) || v.is_a?(FalseClass) }
      raise "Invalid feature flag value(s): #{invalid_values}" if invalid_values.any?

      feature_flag_overrides.each do |flag_name, flag_value|
        feature_flag = FeatureFlag.find(name: flag_name.to_s)
        if feature_flag
          next if feature_flag.enabled == flag_value
        else
          feature_flag = FeatureFlag.new(name: flag_name.to_s)
        end

        feature_flag.enabled = flag_value
        feature_flag.save
      end

      @feature_flag_overrides = feature_flag_overrides
    end

    def self.config_overridden?(feature_flag_name)
      @feature_flag_overrides && @feature_flag_overrides.keys.include?(feature_flag_name)
    end

    private_class_method :admin?
  end
end
