Sequel.extension :migration
require 'wyrm/module'

# needs dst_db for mutate operations
# and src_db for fetch operations
# src_db must have extension(:schema_dumper)
module Wyrm
  module SchemaTools
    # some includers will need to provide a different implementation for this.
    def same_db
      respond_to?( :dst_db ) && respond_to?( :src_db ) && dst_db&.database_type == src_db&.database_type
    end

    def schema_migration
      @schema_migration ||= src_db.dump_schema_migration indexes: false, :same_db => same_db
    end

    # dump single table including indexes, but ignore foreign keys
    def table_migration( table )
      src_db.dump_table_schema table, indexes: true, :same_db => same_db
    end

    def index_migration
      @index_migration ||= src_db.dump_indexes_migration :same_db => same_db
    end

    def fk_migration
      @fk_migration ||= src_db.dump_foreign_key_migration :same_db => same_db
    end

    def drop_table_options
      @drop_table_options ||=
      begin
        if dst_db.database_type == :postgres
          {cascade: true}
        else
          {}
        end
      end
    end

    # Delete given tables.
    # Recurse if there are foreign keys preventing table deletion.
    # This implementation will fail for tables with mutual foreign keys.
    # TODO maybe this should use the schema down migration?
    def drop_tables( tables )
      foreign_keyed_tables = []
      tables.each do |table_name|
        begin
          logger.debug "dropping #{table_name}"
          dst_db.drop_table? table_name, drop_table_options

        rescue Sequel::ForeignKeyConstraintViolation => ex
          foreign_keyed_tables << table_name

        rescue Sequel::DatabaseError => ex
          # Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails
          # SQLite3::ConstraintException: FOREIGN KEY constraint failed==
          if ex.message =~ /foreign key constraint fail/i
            foreign_keyed_tables << table_name
          else
            raise
          end
        end
      end

      # this should be temporary
      if tables.any? && tables.sort == foreign_keyed_tables.sort
        raise "can't remove #{tables.inspect} because they have mutual foreign keys"
      end

      # recursively delete tables. Shuffle as kak workaround for dependency loops.
      drop_tables foreign_keyed_tables.shuffle unless foreign_keyed_tables.empty?
    end

    def create_tables
      logger.info "creating tables"
      eval( schema_migration ).apply dst_db, :up
    end

    def create_indexes
      # create indexes and foreign keys, and reset sequences
      logger.info "creating indexes"
      eval( index_migration ).apply dst_db, :up

      logger.info "creating foreign keys"
      eval( fk_migration ).apply dst_db, :up

      if dst_db.database_type == :postgres
        logger.info "reset primary key sequences"
        dst_db.tables.each{|t| dst_db.reset_primary_key_sequence(t)}
        logger.info "Primary key sequences reset successfully"
      end
    end
  end
end
