require 'models/helpers/metadata_helpers'
require 'actions/labels_update'
require 'actions/annotations_update'

module VCAP::CloudController
  class AppUpdate
    class DropletNotFound < StandardError; end
    class InvalidApp < StandardError; end

    def initialize(user_audit_info, manifest_triggered: false, runners: nil)
      @user_audit_info = user_audit_info
      @logger = Steno.logger('cc.action.app_update')
      @manifest_triggered = manifest_triggered
      @runners = runners || CloudController::DependencyLocator.instance.runners
    end

    def update(app, message, lifecycle)
      validate_not_changing_lifecycle_type!(app, lifecycle)

      app.db.transaction do
        app.lock!

        app.name = message.name if message.requested?(:name)

        LabelsUpdate.update(app, message.labels, AppLabelModel)
        AnnotationsUpdate.update(app, message.annotations, AppAnnotationModel)

        app.save

        raise InvalidApp.new(lifecycle.errors.full_messages.join(', ')) unless lifecycle.valid?

        lifecycle.update_lifecycle_data_model(app)

        raise CloudController::Errors::ApiError.new_from_details('CustomBuildpacksDisabled') if using_disabled_custom_buildpack?(app)

        Repositories::AppEventRepository.new.record_app_update(
          app,
          app.space,
          @user_audit_info,
          message.audit_hash,
          manifest_triggered: @manifest_triggered
        )

        # update process timestamp to trigger convergence if sending fails
        app.processes.each(&:save) if updating_metric_tags?(message)
      end

      if updating_metric_tags?(message)
        app.processes.each do |process|
          @runners.runner_for_process(process).update_metric_tags if process.state == ProcessModel::STARTED
        rescue Diego::Runner::CannotCommunicateWithDiegoError => e
          @logger.error("failed communicating with diego backend: #{e.message}")
        end
      end

      app
    rescue Sequel::ValidationFailed => e
      raise InvalidApp.new(e.message)
    end

    private

    def using_disabled_custom_buildpack?(app)
      app.lifecycle_data.using_custom_buildpack? && custom_buildpacks_disabled?
    end

    def custom_buildpacks_disabled?
      Config.config.get(:disable_custom_buildpacks)
    end

    def updating_metric_tags?(message)
      message.requested?(:name) && Config.config.get(:update_metric_tags_on_rename)
    end

    def validate_not_changing_lifecycle_type!(app, lifecycle)
      return if app.lifecycle_type == lifecycle.type

      raise InvalidApp.new("Lifecycle type cannot be changed from #{app.lifecycle_type} to #{lifecycle.type}")
    end

    def existing_environment_variables_for(app)
      app.environment_variables.nil? ? {} : app.environment_variables.symbolize_keys
    end
  end
end
