module Neo4j
  module Migrations
    class Base
      include Neo4j::Migrations::Helpers
      include Neo4j::Migrations::Helpers::Schema
      include Neo4j::Migrations::Helpers::IdProperty
      include Neo4j::Migrations::Helpers::Relationships

      def initialize(migration_id, options = {})
        @migration_id = migration_id
        @silenced = options[:silenced]
      end

      def migrate(method)
        Benchmark.realtime do
          method == :up ? migrate_up : migrate_down
        end
      end

      def up
        fail NotImplementedError
      end

      def down
        fail NotImplementedError
      end

      private

      def migrate_up
        schema = SchemaMigration.create!(migration_id: @migration_id, incomplete: true)
        begin
          run_migration(:up)
        rescue StandardError => e
          schema.destroy if transactions?
          handle_migration_error!(e)
        else
          schema.update!(incomplete: nil)
        end
      end

      def migrate_down
        schema = SchemaMigration.find_by!(migration_id: @migration_id)
        schema.update!(incomplete: true)
        begin
          run_migration(:down)
        rescue StandardError => e
          schema.update!(incomplete: nil) if transactions?
          handle_migration_error!(e)
        else
          schema.destroy
        end
      end

      def run_migration(direction)
        migration_transaction { log_queries { public_send(direction) } }
      end

      def handle_migration_error!(e)
        fail e unless e.message =~ /Cannot perform data updates in a transaction that has performed schema updates./
        fail MigrationError,
             "#{e.message}. Please add `disable_transactions!` in your migration file."
      end

      def migration_transaction(&block)
        ActiveBase.run_transaction(transactions?, &block)
      end

      def log_queries
        subscriber = Neo4j::Transaction.subscribe_to_query(&method(:output))
        yield
      ensure
        ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
      end
    end
  end
end