module Dunlop::OverdueHandling
  @registry = {}
  @final_states = %w[overdue completed rejected]

  def self.registry
    @registry
  end

  def self.final_states
    @final_states
  end

  def self.final_states=(states)
    @final_states = states
  end

  def self.define(&block)
    @registry = {} # empty the registry
    definition_proxy = DefinitionProxy.new
    definition_proxy.instance_eval(&block)
  end

  def self.handle_overdue!
    registry.each do |workflow_step_name, config|
      base_scope = WorkflowInstance.all #where.not(state: final_states)
      base_scope = base_scope.where(sti_type: config.scenarios.map{|s| "WorkflowInstance::#{s.to_s.classify}"}) if config.scenarios.any?
      base_scope = base_scope.joins(workflow_step_name).where.not("#{workflow_step_name.to_s.pluralize}.state" => final_states) #.includes(workflow_step_name)
      if config.from.is_nested?
        base_scope = base_scope.joins(config.from.key) if config.from.key != workflow_step_name
      end
      base_scope = base_scope.where("#{config.date_target_class.table_name}.#{config.date_target_column} <= ?", Date.current - config.time)

      base_scope.find_each do |workflow_instance|
        next unless workflow_step = workflow_instance.public_send(workflow_step_name)
        workflow_step.is_overdue!
      end
    end
  end

  class DefinitionProxy
    def when_workflow_step(workflow_step, options = {}, &block)
      raise "Workflow step #{workflow_step} does not exist" unless WorkflowInstance.possible_workflow_step_names.include?(workflow_step)
      workflow_step_proxy = WorkflowStepProxy.new(workflow_step, options)
      workflow_step_proxy.instance_eval(&block) if block
      workflow_step_proxy.register_callbacks!
      Dunlop::OverdueHandling.registry[workflow_step] = workflow_step_proxy
    end

    def the_final_states_are(states)
      Dunlop::OverdueHandling.final_states = states
    end
  end

  class WorkflowStepProxy
    attr_reader :workflow_step, :options, :overdue_target_class
    def initialize(workflow_step, options)
      @workflow_step, @options = workflow_step, options
      @overdue_target_class = workflow_step.to_s.classify.constantize
    end

    def date_target_class
      @date_target_class ||= from.is_nested? ? from.key.to_s.classify.constantize : WorkflowInstance
    end

    def date_target_column
      @date_target_column ||= from.field
    end

    def scenarios
      options[:scenarios] || []
    end

    def from
      WorkflowStepBaseDate.new(workflow_step, options[:from])
    end

    def time
      options[:is]
    end

    #TODO: find a simple way to do this!!!
    def register_callbacks!
      date_target_classes = [date_target_class] #TODO: only implement on specific scenarios if given as argument
      date_column = date_target_column
      workflow_step_name = workflow_step # make local scope for after_save definition
      overdue_time = time
      date_target_classes.each do |target_class|
        target_class.after_save do |record|
          if saved_changes[date_column]
            target_record = record.workflow_instance.public_send(workflow_step_name)
            if date = record.public_send(date_column)
              if date <= Date.current - overdue_time
                target_record.is_overdue! if target_record.pending? or target_record.processing?
              else
                target_record.process! if target_record.overdue?
              end
            else
              target_record.process! if target_record.overdue?
            end
          end
        end
      end
    end
  end

  class WorkflowStepBaseDate
    attr_reader :workflow_step, :config
    def initialize(workflow_step, config)
      @workflow_step, @config = workflow_step, config
    end

    def is_nested?
      config.is_a?(Hash)
    end

    def key
      is_nested? ? config.keys.first : config
    end

    def field
      is_nested? ? config.values.first : config
    end
  end
end
