class VersionRecorder
  EVENT_TYPES = ['create', 'update', 'discard', 'restore'].freeze

  def initialize record:, event_type:, created_by_user:
    @record = record
    @event_type = event_type.to_s
    @created_by_user = created_by_user

    validate_event_type!
  end

  def record!
    raise "#{ record_class.name } must be persisted" unless @record.persisted?

    ApplicationRecord.transaction do
      @record = record_class.unscoped.lock.find(@record.id)
      latest = latest_version

      validate_version_sequence!(latest)

      attrs = snapshot_attributes

      if @event_type == 'update' && latest && same_snapshot?(latest, attrs)
        return latest
      end

      version = version_class.create!(
          base_attributes(latest).merge(record_key => @record).merge(attrs))

      update_record_version_no!(version.version_no)

      version
    end
  end

  private

  def latest_version = versions.order(version_no: :desc).first

  def versions = @record.public_send(version_association)

  def base_attributes latest
    { version_no: (latest&.version_no || 0) + 1,
      event_type: @event_type,
      created_at: Time.current,
      created_by_user: @created_by_user }
  end

  def update_record_version_no! version_no
    @record.update_columns(version_no:)
    @record.version_no = version_no
  end

  def validate_version_sequence! latest
    if !(latest) && @event_type != 'create'
      raise "#{ version_class.name } first event must be create"
    end

    if @event_type == 'create' && latest
      raise "#{ version_class.name } create event already exists"
    end

    return unless latest

    if @record.version_no != latest.version_no
      raise ("#{ record_class.name }##{ @record.id } version_no is #{ @record.version_no }, " +
             "but latest #{ version_class.name } version_no is #{ latest.version_no }")
    end
  end

  def same_snapshot? version, attrs
    attrs.all? { |k, v| version.public_send(k) == v }
  end

  def validate_event_type!
    return if event_types.include?(@event_type)

    raise ArgumentError, "Invalid event_type: #{ @event_type }"
  end

  def version_class = raise NotImplementedError
  def version_association = raise NotImplementedError
  def record_key = raise NotImplementedError
  def snapshot_attributes = raise NotImplementedError

  def record_class = @record.class
  def event_types = self.class::EVENT_TYPES
end
