# frozen_string_literal: true

module Dynflow
  class DelayedPlan < Serializable
    include Algebrick::TypeCheck

    attr_reader :execution_plan_uuid, :start_before
    attr_accessor :frozen, :start_at

    def initialize(world, execution_plan_uuid, start_at, start_before, args_serializer, frozen)
      @world               = Type! world, World
      @execution_plan_uuid = Type! execution_plan_uuid, String
      @start_at            = Type! start_at, Time, NilClass
      @start_before        = Type! start_before, Time, NilClass
      @args_serializer     = Type! args_serializer, Serializers::Abstract
      @frozen              = Type! frozen, Algebrick::Types::Boolean
    end

    def execution_plan
      @execution_plan ||= @world.persistence.load_execution_plan(@execution_plan_uuid)
    end

    def plan
      execution_plan.root_plan_step.load_action
      execution_plan.generate_action_id
      execution_plan.generate_step_id
      execution_plan.plan(*@args_serializer.perform_deserialization!)
    end

    def timeout
      error("Execution plan could not be started before set time (#{@start_before})", 'timeout')
    end

    def error(message, history_entry = nil)
      execution_plan.root_plan_step.state = :error
      execution_plan.root_plan_step.error = ::Dynflow::ExecutionPlan::Steps::Error.new(message)
      execution_plan.root_plan_step.save
      execution_plan.update_state :stopped, history_notice: history_entry
    end

    def cancel
      execution_plan.root_plan_step.state = :cancelled
      execution_plan.root_plan_step.save
      execution_plan.update_state :stopped, history_notice: "Delayed task cancelled"
      @world.persistence.delete_delayed_plans(:execution_plan_uuid => @execution_plan_uuid)
      return true
    end

    def execute(future = Concurrent::Promises.resolvable_future)
      @world.execute(@execution_plan_uuid, future)
      ::Dynflow::World::Triggered[@execution_plan_uuid, future]
    end

    def to_hash
      recursive_to_hash :execution_plan_uuid => @execution_plan_uuid,
                        :start_at            => @start_at,
                        :start_before        => @start_before,
                        :serialized_args     => @args_serializer.serialized_args,
                        :args_serializer     => @args_serializer.class.name,
                        :frozen              => @frozen
    end

    # Retrieves arguments from the serializer
    #
    # @return [Array] array of the original arguments
    def args
      @args_serializer.perform_deserialization! if @args_serializer.args.nil?
      @args_serializer.args
    end

    # @api private
    def self.new_from_hash(world, hash, *args)
      serializer = Utils.constantize(hash[:args_serializer]).new(nil, hash[:serialized_args])
      self.new(world,
        hash[:execution_plan_uuid],
        string_to_time(hash[:start_at]),
        string_to_time(hash[:start_before]),
        serializer,
        hash[:frozen] || false)
    rescue NameError => e
      error(e.message)
    end
  end
end
