require 'models/runtime/helpers/service_operation_mixin'

module VCAP::CloudController
  class ServiceKey < Sequel::Model
    include ServiceOperationMixin

    class InvalidAppAndServiceRelation < StandardError; end

    many_to_one :service_instance

    one_to_one :service_key_operation

    one_to_many :labels, class: 'VCAP::CloudController::ServiceKeyLabelModel', key: :resource_guid, primary_key: :guid
    one_to_many :annotations, class: 'VCAP::CloudController::ServiceKeyAnnotationModel', key: :resource_guid, primary_key: :guid
    add_association_dependencies labels: :destroy
    add_association_dependencies annotations: :destroy

    export_attributes :name, :service_instance_guid, :credentials

    import_attributes :name, :service_instance_guid, :credentials

    delegate :service, :service_plan, to: :service_instance

    plugin :after_initialize

    set_field_as_encrypted :credentials

    def credhub_reference?
      !!credhub_reference
    end

    def credhub_reference
      credentials.present? ? credentials['credhub-ref'] : nil
    end

    delegate :in_suspended_org?, to: :space

    delegate :space, to: :service_instance

    def around_save
      yield
    rescue Sequel::UniqueConstraintViolation => e
      raise e unless e.message.include?('svc_key_name_instance_id_index')

      errors.add(%i[name service_instance_id], :unique)
      raise validation_failed_error
    end

    def validate
      validates_presence :name
      validates_presence :service_instance
      validates_unique %i[name service_instance_id]

      return unless service_instance

      space_ids_for_org_dataset = Space.where(organization_id: space.organization_id).select(:id)
      MaxServiceKeysPolicy.new(
        self,
        ServiceKey.filter(service_instance: ServiceInstance.where(space_id: space_ids_for_org_dataset)),
        space.organization.quota_definition,
        :service_keys_quota_exceeded
      ).validate
      MaxServiceKeysPolicy.new(
        self,
        ServiceKey.filter(service_instance: ServiceInstance.where(space_id: space.id)),
        space.space_quota_definition,
        :service_keys_space_quota_exceeded
      ).validate
    end

    def credentials_with_serialization=(val)
      self.credentials_without_serialization = Oj.dump(val)
    end
    alias_method 'credentials_without_serialization=', 'credentials='
    alias_method 'credentials=', 'credentials_with_serialization='

    def credentials_with_serialization
      string = credentials_without_serialization
      return if string.blank?

      Oj.load(string)
    end
    alias_method 'credentials_without_serialization', 'credentials'
    alias_method 'credentials', 'credentials_with_serialization'

    def self.user_visibility_filter(user)
      { service_instance: ServiceInstance.dataset.filter({ space: user.spaces_dataset }) }
    end

    def after_initialize
      super
      self.guid ||= SecureRandom.uuid
    end

    def logger
      @logger ||= Steno.logger('cc.models.service_key')
    end

    def last_operation
      service_key_operation
    end

    def save_with_attributes_and_new_operation(attributes, operation)
      ServiceKey.db.transaction do
        lock!
        set(attributes.except(:parameters, :route_services_url, :endpoints))
        save_changes

        last_operation.destroy if last_operation

        # it is important to create the service key operation with the service key
        # instead of doing self.service_key_operation = x
        # because mysql will deadlock when requests happen concurrently otherwise.
        ServiceKeyOperation.create(operation.merge(service_key_id: id))
        service_key_operation(reload: true)
      end
      self
    end
  end
end
