namespace :gs_cluster do
  desc "Sync local data to other gs cluster nodes."
  task :push_waiting_data_to_other_nodes => :environment do
    infinity_loop_protection_counter = GsClusterSyncLogEntry.where(:homebase_ip_address => GsParameter.get('HOMEBASE_IP_ADDRESS'), 
    	                                                           :waiting_to_be_synced => true).count + 10

    # One bite at a time.
    #
    while GsClusterSyncLogEntry.where(:homebase_ip_address => GsParameter.get('HOMEBASE_IP_ADDRESS'), 
    	                                :waiting_to_be_synced => true).any? && 
                                      infinity_loop_protection_counter > 0
      GsClusterSyncLogEntry.where(:homebase_ip_address => GsParameter.get('HOMEBASE_IP_ADDRESS'), 
      	                          :waiting_to_be_synced => true).first.populate_other_cluster_nodes
      infinity_loop_protection_counter -= 1
    end

  end

  desc "Reset gs_cluster_sync_log."
  task :reset_sync_log => :environment do
    GsClusterSyncLogEntry.destroy_all

    User.where('is_native IS NOT FALSE').each do |user|
      puts("Processing User=#{user.id}/#{user.uuid} - #{user.user_name}")
      user.create_on_other_gs_nodes
    end

    SipAccount.where('is_native IS NOT FALSE').each do |sip_account|
      puts("Processing SipAccount=#{sip_account.id}/#{sip_account.uuid} - #{sip_account.auth_name}");
      sip_account.create_on_other_gs_nodes
    end

    PhoneNumber.where('is_native IS NOT FALSE AND phone_numberable_type IN ("SipAccount", "Conference", "FaxAccount", "Callthrough", "HuntGroup", "AutomaticCallDistributor")').each do |phone_number|
      puts("Processing PhoneNumber=#{phone_number.id}/#{phone_number.uuid} - #{phone_number.number}");
      phone_number.create_on_other_gs_nodes
    end
  end

  desc "Pull objects from nodes."
  task :pull => :environment do
    local_node = GsNode.where(:ip_address => GsParameter.get('HOMEBASE_IP_ADDRESS')).first
    GsNode.where(:accepts_updates_from => true).each do |remote_node|
      if remote_node.id == local_node.id
        next
      end

      puts "Processing node: #{remote_node.name}"
      pull_node(remote_node, local_node)
    end
  end

  def pull_node(remote_node, local_node)
    require 'nokogiri'
    require 'open-uri'

    is_native = false
    remote_site = remote_node.site
    local_node_id = local_node.id
    last_sync = remote_node.last_sync.to_i

    remote_objects(remote_site, local_node_id, last_sync, Tenant).each do |tenant|
      puts "Processing Tenant: #{tenant[:name]}"
    end

    remote_objects(remote_site, local_node_id, last_sync, UserGroup).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      tenant = Tenant.where(:name => attributes[:tenant]).first
      process_object(UserGroup, tenant.user_groups, UserGroup.where(:name => attributes[:name], :tenant_id => tenant.try(:id)).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, User).each do |remote_object|
      attributes = make_hash(remote_object.attributes)

      tenant = Tenant.where(:name => attributes[:current_tenant]).first
      attributes[:language_id] = Language.where(:code => attributes[:language]).first.try(:id)
      attributes.delete(:language)
      attributes.delete(:current_tenant)

      if tenant
        if ! attributes[:gs_node].blank?
          attributes[:gs_node_id] = GsNode.where(:name => attributes[:gs_node]).first.try(:id)
          attributes.delete(:gs_node)
        end

        process_object(User, tenant.users, User.where(:uuid => attributes[:uuid]).first, attributes, { :is_native => is_native })
      else
        $stderr.puts "NO_PROCESSING User #{attributes[:uuid]} - no current tenant"
      end
    end

    remote_objects(remote_site, local_node_id, last_sync, UserGroupMembership).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:user_id] = User.where(:uuid => attributes[:user_uuid]).first.try(:id)
      attributes[:user_group_id] = UserGroup.where(:name => attributes[:user_group]).first.try(:id)
      attributes.delete(:user_uuid)
      attributes.delete(:user_group)

      if attributes[:user_id] && attributes[:user_group_id]
        process_object(UserGroupMembership, UserGroupMembership, UserGroupMembership.where(:user_id => attributes[:user_id], :user_group_id => attributes[:user_group_id]).first, attributes)
      end
    end

    remote_objects(remote_site, local_node_id, last_sync, SipAccount).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:tenant_id] = Tenant.where(:name => attributes[:tenant]).first.try(:id)
      attributes[:sip_domain] = SipDomain.where(:host => attributes[:sip_domain]).first
      
      if ! attributes[:sip_accountable_uuid].blank?
        attributes[:sip_accountable_id] = attributes[:sip_accountable_type].constantize.where(:uuid => attributes[:sip_accountable_uuid]).first.try(:id)
      end

      if attributes[:sip_accountable_id]
        if ! attributes[:gs_node].blank?
          attributes[:gs_node_id] = GsNode.where(:name => attributes[:gs_node]).first.try(:id)
          attributes.delete(:gs_node)
        end

        attributes.delete(:sip_accountable_uuid)
        attributes.delete(:tenant)
        process_object(SipAccount, SipAccount, SipAccount.where(:uuid => attributes[:uuid]).first, attributes, { :is_native => is_native })
      end
    end

    remote_objects(remote_site, local_node_id, last_sync, Conference).each do |remote_object|
      attributes = make_hash(remote_object.attributes)

      if ! attributes[:conferenceable_uuid].blank?
        attributes[:conferenceable_id] = attributes[:conferenceable_type].constantize.where(:uuid => attributes[:conferenceable_uuid]).first.try(:id)
      end
      attributes.delete(:conferenceable_uuid)

      process_object(Conference, Conference, Conference.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, FaxAccount).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:tenant_id] = Tenant.where(:name => attributes[:tenant]).first.try(:id)
      if ! attributes[:fax_accountable_uuid].blank?
        attributes[:fax_accountable_id] = attributes[:fax_accountable_type].constantize.where(:uuid => attributes[:fax_accountable_uuid]).first.try(:id)
      end
      attributes.delete(:fax_accountable_uuid)
      attributes.delete(:tenant)
      process_object(FaxAccount, FaxAccount, FaxAccount.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, PhoneBook).each do |remote_object|
      attributes = make_hash(remote_object.attributes)

      if ! attributes[:phone_bookable_uuid].blank?
        attributes[:phone_bookable_id] = attributes[:phone_bookable_type].constantize.where(:uuid => attributes[:phone_bookable_uuid]).first.try(:id)
      end
      attributes.delete(:phone_bookable_uuid)
      process_object(PhoneBook, PhoneBook, PhoneBook.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, PhoneBookEntry).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:phone_book_id] = PhoneBook.where(:uuid => attributes[:phone_book_uuid]).first.try(:id)
      attributes.delete(:phone_book_uuid)
      process_object(PhoneBookEntry, PhoneBookEntry, PhoneBookEntry.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, PhoneNumber).each do |remote_object|
      attributes = make_hash(remote_object.attributes)

      if ! attributes[:phone_numberable_uuid].blank?
        attributes[:phone_numberable_id] = attributes[:phone_numberable_type].constantize.where(:uuid => attributes[:phone_numberable_uuid]).first.try(:id)
      end

      if ! attributes[:gs_node].blank?
        attributes[:gs_node_id] = GsNode.where(:name => attributes[:gs_node]).first.try(:id)
        attributes.delete(:gs_node)
      end

      if !attributes[:phone_numberable_id]
        puts "WARNING PhoneNumber #{attributes[:number]} has no local parent object #{attributes[:phone_numberable_type]}/#{attributes[:phone_numberable_uuid]}"
      end

      attributes.delete(:phone_numberable_uuid)
      process_object(PhoneNumber, PhoneNumber, PhoneNumber.where(:uuid => attributes[:uuid]).first, attributes, { :is_native => is_native })
    end

    remote_objects(remote_site, local_node_id, last_sync, CallForward).each do |remote_object|
      attributes = make_hash(remote_object.attributes)

      attributes[:phone_number_id] = PhoneNumber.where(:uuid => attributes[:phone_number_uuid]).first.try(:id)
      
      if ! attributes[:call_forwardable_uuid].blank?
        attributes[:call_forwardable_id] = attributes[:call_forwardable_type].constantize.where(:uuid => attributes[:call_forwardable_uuid]).first.try(:id)
        attributes.delete(:call_forwardable_uuid)
      end

      attributes[:call_forward_case_id] = CallForwardCase.where(:value => attributes[:service]).first.try(:id)
      
      attributes.delete(:phone_number_uuid)
      attributes.delete(:service)

      process_object(CallForward, CallForward, CallForward.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, Softkey).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:sip_account_id] = SipAccount.where(:uuid => attributes[:sip_account_uuid]).first.try(:id)

      if ! attributes[:softkeyable_uuid].blank?
        attributes[:softkeyable_id] = attributes[:softkeyable_type].constantize.where(:uuid => attributes[:softkeyable_uuid]).first.try(:id)
      end

      attributes[:softkey_function_id] = SoftkeyFunction.where(:name => attributes[:function]).first.try(:id)
      attributes.delete(:sip_account_uuid)
      attributes.delete(:softkeyable_uuid)
      attributes.delete(:softkey_function)
      process_object(Softkey, Softkey, Softkey.where(:uuid => attributes[:uuid]).first, attributes)
    end

    remote_objects(remote_site, local_node_id, last_sync, Ringtone).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      if ! attributes[:ringtoneable_uuid].blank?
        attributes[:ringtoneable_id] = attributes[:ringtoneable_type].constantize.where(:uuid => attributes[:ringtoneable_uuid]).first.try(:id)
      end

      if !attributes[:ringtoneable_id]
        puts "WARNING Ringtone #{attributes[:number]} has no local parent object #{attributes[:ringtoneable_type]}/#{attributes[:ringtoneable_uuid]}"
      else
        attributes.delete(:ringtoneable_uuid)
        process_object(Ringtone, Ringtone, Ringtone.where(:ringtoneable_type => attributes[:ringtoneable_type], :ringtoneable_id => attributes[:ringtoneable_id]).first, attributes)
      end
    end

    remote_objects(remote_site, local_node_id, last_sync, ConferenceInvitee).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      attributes[:conference_id] = Conference.where(:uuid => attributes[:conference_uuid]).first.try(:id)
      attributes[:phone_number] = PhoneNumber.where(:uuid => attributes[:phone_number_uuid]).first
      if !attributes[:conference_id]
        puts "WARNING ConferenceInvitee #{attributes[:uuid]} has no local Conference object #{attributes[:conference_uuid]}"
      else
        attributes[:phone_book_entry_id] = PhoneBookEntry.where(:uuid => attributes[:phone_book_entry_uuid]).first.try(:id)
        attributes.delete(:conference_uuid)
        attributes.delete(:phone_number_uuid)
        attributes.delete(:phone_book_entry_uuid)
        process_object(ConferenceInvitee, ConferenceInvitee, ConferenceInvitee.where(:uuid => attributes[:uuid]).first, attributes)
      end
    end

    #remote_objects(remote_site, local_node_id, last_sync, FaxDocument).each do |remote_object|
    #  attributes = make_hash(remote_object.attributes)
    #  attributes[:fax_account_id] = FaxAccount.where(:uuid => attributes[:fax_account_uuid]).first.try(:id)
    #  attributes[:fax_resolution_id] = FaxResolution.where(:resolution_value => attributes[:fax_resolution]).first.try(:id)
    #  attributes.delete(:fax_account_uuid)
    # attributes.delete(:fax_resolution)
    #  process_object(FaxDocument, FaxDocument, FaxDocument.where(:uuid => attributes[:uuid]).first, attributes)
    #end

    #remote_objects(remote_site, local_node_id, last_sync, CallHistory).each do |remote_object|
    #  attributes = make_hash(remote_object.attributes)
    #  process_object(CallHistory, CallHistory, CallHistory.where(:caller_channel_uuid => attributes[:caller_channel_uuid], :call_historyable_type => attributes[:caller_channel_type], :call_historyable_id => call_historyable.try(:id)).first, attributes)
    #end

    remote_objects(remote_site, local_node_id, last_sync, DeletedItem).each do |remote_object|
      attributes = make_hash(remote_object.attributes)
      deleted_item = remote_object[:class_name].constantize.where(:uuid => attributes[:uuid]).first

      if deleted_item
        print "DELETE #{deleted_item.class.to_s} #{deleted_item.to_s} : "

        if deleted_item.destroy
          puts "OK"
        else
          $stderr.puts "Couldn't delete #{deleted_item.class.to_s}. #{deleted_item.errors.inspect}"
        end
      else
        puts "NO_DELETE #{remote_object[:class_name]} #{remote_object[:uuid]}"
      end
    end

    if ! remote_node.synced
      $stderr.puts "Errors updating node #{remote_node.name}. #{remote_node.errors.inspect}"
    end
  end

  def make_hash(attributes, new_hash = Hash.new)
    attributes.each do |key, value|
      new_hash[key.to_sym] = value.to_s
    end
    return new_hash
  end

  def remote_objects(remote_site, local_node_id, last_sync, object_class)
    class_name = object_class.to_s.underscore
    section_name = class_name.pluralize    
    doc = Nokogiri::XML(open("#{remote_site}/gs_nodes/#{local_node_id}/sync.xml?newer=#{last_sync}&image=false&class=#{section_name}", :proxy => nil, :read_timeout => 120))
    return doc.xpath("//gemeinschaft_sync/#{section_name}/#{class_name}")
  end

  def process_object(object_class, belongs_to, local_object, attributes, local_attributes = Hash.new)
    if local_object
      if local_object.updated_at < attributes[:updated_at]
        print "UPDATE #{object_class.to_s} #{local_object.to_s} : "
        update_object(local_object, attributes, local_attributes)
      else
        print "NO_UPDATE #{object_class.to_s}: #{local_object.to_s} - last update: #{local_object.updated_at}, remote: #{attributes[:updated_at]}"
      end
    else
      print "CREATE #{object_class.to_s} #{attributes[:name].to_s} #{attributes[:uuid].to_s} : "
      create_object(belongs_to, attributes, local_attributes)
    end
    puts "."
  end

  def create_object(object_class, attributes, local_attributes)
    attributes = attributes.merge(local_attributes)

    new_local_copy = object_class.create(attributes, :without_protection => true)
    if new_local_copy && new_local_copy.errors.count == 0
      print "Created object, #{new_local_copy.class.to_s} #{new_local_copy.to_s}"
      return true
    else
      $stderr.print "Couldn't create object. #{new_local_copy.errors.messages.inspect}"
      return false
    end
  end

  def update_object(local_object, attributes, local_attributes)
    attributes = attributes.merge(local_attributes)

    if local_object.update_attributes(attributes, :without_protection => true)
      print "Updated #{local_object.class.to_s}, ID #{local_object.id}."
      return true
    else
      $stderr.print "Couldn't update UserGroup. #{local_user_group.errors.inspect}"
      return true
    end
  end

  class DeletedItem
  end
end
