# frozen_string_literal: true

require 'active_record/tasks/database_tasks'

module ArJdbc
  module Tasks # :nodoc:
    class MSSQLDatabaseTasks # :nodoc:
      delegate :connection, to: ActiveRecord::Base
      delegate :establish_connection, to: ActiveRecord::Base
      delegate :clear_active_connections!, to: ActiveRecord::Base

      def self.using_database_configurations?
        true
      end

      def initialize(db_config)
        @db_config = db_config
        @configuration_hash = db_config.configuration_hash
      end

      def create
        establish_master_connection
        connection.create_database(db_config.database, creation_options)
        establish_connection(db_config)
      rescue ActiveRecord::StatementInvalid => e
        case e.message
        when /database .* already exists/i
          raise ActiveRecord::Tasks::DatabaseAlreadyExists
        else
          raise
        end
      end

      def drop
        establish_master_connection
        connection.drop_database(db_config.database)
      end

      def charset
        connection.charset
      end

      def collation
        connection.collation
      end

      def purge
        clear_active_connections!
        drop
        create
      end

      def structure_dump(filename, _extra_flags)
        args = prepare_command_options

        args.concat(["-f #{filename}"])

        run_cmd('mssql-scripter', args, 'dumping')
      end

      def structure_load(filename, _extra_flags)
        args = prepare_command_options

        args.concat(["-i #{filename}"])

        run_cmd('mssql-cli', args, 'loading')
      end

      private

      attr_reader :db_config, :configuration_hash

      def creation_options
        {}.tap do |options|
          options[:collation] = configuration_hash[:collation] if configuration_hash.include?(:collation)

          # azure creation options
          options[:azure_maxsize] = configuration_hash[:azure_maxsize] if configuration_hash.include?(:azure_maxsize)
          options[:azure_edition] = configuration_hash[:azure_edition] if configuration_hash.include?(:azure_edition)

          if configuration_hash.include?(:azure_service_objective)
            options[:azure_service_objective] = configuration_hash[:azure_service_objective]
          end
        end
      end

      def establish_master_connection
        establish_connection(configuration_hash.merge(database: 'master'))
      end

      def prepare_command_options
        {
          server: '-S',
          database: '-d',
          username: '-U',
          password: '-P'
        }.map { |option, arg| "#{arg} #{config_for_cli[option]}" }
      end

      def config_for_cli
        {}.tap do |options|
          if configuration_hash[:host].present? && configuration_hash[:port].present?
            options[:server] = "#{configuration_hash[:host]},#{configuration_hash[:port]}"
          elsif configuration_hash[:host].present?
            options[:server] = configuration_hash[:host]
          end

          options[:database] = configuration_hash[:database] if configuration_hash[:database].present?
          options[:username] = configuration_hash[:username] if configuration_hash[:username].present?
          options[:password] = configuration_hash[:password] if configuration_hash[:password].present?
        end
      end

      def run_cmd(cmd, args, action)
        fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
      end

      def run_cmd_error(cmd, args, action)
        msg = +"failed to execute:\n"
        msg << "#{cmd} #{args.join(' ')}\n\n"
        msg << "Failed #{action} structure, please check the output above for any errors"
        msg << " and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
        msg
      end
    end

    module DatabaseTasksMSSQL
      extend ActiveSupport::Concern

      module ClassMethods

      def check_protected_environments!
        super
      rescue ActiveRecord::JDBCError => e
        case e.message
        when /cannot open database .* requested by the login/i
        else
          raise
        end
      end

      end
    end

    ActiveRecord::Tasks::DatabaseTasks.send(:include, DatabaseTasksMSSQL)
  end
end