require 'active_support/concern'
require 'neo4j/migration'

if !defined?(Rails) && !Rake::Task.task_defined?('environment')
  desc 'Run a script against the database to perform system-wide changes'
  task :environment do
    require 'neo4j/session_manager'
    require 'ostruct'
    neo4j_url = ENV['NEO4J_URL'] || 'http://localhost:7474'
    $LOAD_PATH.unshift File.dirname('./')
    Neo4j::ActiveBase.on_establish_session do
      type = neo4j_url =~ /^bolt/ ? :bolt : :http
      Neo4j::SessionManager.open_neo4j_session(type, neo4j_url)
    end
  end
end

namespace :neo4j do
  task :allow_migrations do
    Neo4j::Migrations.currently_running_migrations = true
  end
  desc 'Run a script against the database to perform system-wide changes'
  task :legacy_migrate, [:task_name, :subtask] => :environment do |_, args|
    path = Rake.original_dir
    migration_task = args[:task_name]
    task_name_constant = migration_task.split('_').map(&:capitalize).join('')
    begin
      migration_class = "Neo4j::Migration::#{task_name_constant}".constantize
    rescue NameError
      load File.join(path, 'db', 'neo4j-migrate', "#{migration_task}.rb")
      migration_class = task_name_constant.to_s.constantize
    end
    migration = migration_class.new(path)

    subtask = args[:subtask]
    if subtask
      migration.send(subtask)
    else
      migration.migrate
    end
  end

  desc 'A shortcut for neo4j::migrate::all'
  task :migrate do
    Rake::Task['neo4j:migrate:all'].invoke
  end

  # TODO: Make sure these tasks don't run in versions of Neo4j before 3.0
  namespace :schema do
    SCHEMA_YAML_PATH = 'db/neo4j/schema.yml'
    SCHEMA_YAML_COMMENT = <<COMMENT
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of ActiveNode to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.yml definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using neo4j:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

COMMENT

    def check_neo4j_version_3
      if Neo4j::ActiveBase.current_session.version > '3.0.0'
        yield
      else
        puts 'WARNING: This task does not work for versions of Neo4j before 3.0.0'
      end
    end

    desc 'Creates a db/neo4j/schema.yml file which represents the indexes / constraints in the Neo4j DB'
    task dump: :environment do
      check_neo4j_version_3 do
        require 'neo4j/migrations/schema'

        schema_data = Neo4j::Migrations::Schema.fetch_schema_data(Neo4j::ActiveBase.current_session)

        runner = Neo4j::Migrations::Runner.new
        schema_data[:versions] = runner.complete_migration_versions.sort

        FileUtils.mkdir_p(File.dirname(SCHEMA_YAML_PATH))
        File.open(SCHEMA_YAML_PATH, 'w') { |file| file << SCHEMA_YAML_COMMENT + schema_data.to_yaml }

        puts "Dumped updated schema file to #{SCHEMA_YAML_PATH}"
      end
    end

    desc "Loads a db/neo4j/schema.yml file into the database\nOptionally removes schema elements which aren't in the schema.yml file (defaults to false)"
    task :load, [:remove_missing] => :environment do |_t, args|
      check_neo4j_version_3 do
        require 'neo4j/migrations/schema'

        args.with_defaults(remove_missing: false)

        schema_data = YAML.safe_load(File.read(SCHEMA_YAML_PATH), [Symbol])

        Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&method(:puts))

        Neo4j::ActiveBase.run_transaction do
          Neo4j::Migrations::Schema.synchronize_schema_data(Neo4j::ActiveBase.current_session, schema_data, args[:remove_missing])

          runner = Neo4j::Migrations::Runner.new
          runner.mark_versions_as_complete(schema_data[:versions]) # Run in test mode?
        end
      end
    end
  end

  namespace :migrate do
    desc 'Run all pending migrations'
    task all: [:allow_migrations, :environment] do
      runner = Neo4j::Migrations::Runner.new
      runner.all

      Rake::Task['neo4j:schema:dump'].invoke
    end

    desc 'Run a migration given its VERSION'
    task up: [:allow_migrations, :environment] do
      version = ENV['VERSION'] || fail(ArgumentError, 'VERSION is required')
      runner = Neo4j::Migrations::Runner.new
      runner.up version

      Rake::Task['neo4j:schema:dump'].invoke
    end

    desc 'Revert a migration given its VERSION'
    task down: [:allow_migrations, :environment] do
      version = ENV['VERSION'] || fail(ArgumentError, 'VERSION is required')
      runner = Neo4j::Migrations::Runner.new
      runner.down version

      Rake::Task['neo4j:schema:dump'].invoke
    end

    desc 'Print a report of migrations status'
    task status: [:allow_migrations, :environment] do
      runner = Neo4j::Migrations::Runner.new
      runner.status
    end

    desc 'Resolve an incomplete version state'
    task resolve: [:allow_migrations, :environment] do
      version = ENV['VERSION'] || fail(ArgumentError, 'VERSION is required')
      runner = Neo4j::Migrations::Runner.new
      runner.resolve version
    end

    desc 'Resolve an incomplete version state'
    task reset: [:allow_migrations, :environment] do
      version = ENV['VERSION'] || fail(ArgumentError, 'VERSION is required')
      runner = Neo4j::Migrations::Runner.new
      runner.reset version
    end
  end

  desc 'Rollbacks migrations given a STEP number'
  task rollback: [:allow_migrations, :environment] do
    steps = (ENV['STEP'] || 1).to_i
    runner = Neo4j::Migrations::Runner.new
    runner.rollback(steps)

    Rake::Task['neo4j:schema:dump'].invoke
  end

  # Temporary to help people change to 8.0
  desc 'Generates a migration for the specified constraint/index and label/property combination.'
  task :generate_schema_migration, :index_or_constraint, :label, :property_name do |_t, args|
    index_or_constraint, label, property_name = args.values_at(:index_or_constraint, :label, :property_name)

    if !%w(index constraint).include?(index_or_constraint)
      fail "Invalid schema element type: #{index_or_constraint} (should be either `index` or `constraint`)"
    end
    fail 'Label must be specified' if label.blank?
    fail 'Property name must be specified' if property_name.blank?

    migration_class_name = "ForceCreate#{label.camelize}#{property_name.camelize}#{index_or_constraint.capitalize}".gsub('::', '').underscore.camelize

    require 'fileutils'
    FileUtils.mkdir_p('db/neo4j/migrate')
    path = File.join('db/neo4j/migrate', "#{DateTime.now.utc.strftime('%Y%m%d%H%M%S')}_#{migration_class_name.underscore}.rb")

    content = <<-CONTENT
class #{migration_class_name} < Neo4j::Migrations::Base
  def up
    add_#{index_or_constraint} #{label.to_sym.inspect}, #{property_name.to_sym.inspect}, force: true
  end

  def down
    drop_#{index_or_constraint} #{label.to_sym.inspect}, #{property_name.to_sym.inspect}
  end
end
CONTENT

    File.open(path, 'w') { |f| f << content }

    puts "Generated #{path}"
  end
end