require "spec_helper"

describe G5Updatable::LocationsUpdater do
  let(:name) { properties['name'] }
  let(:uid) { properties['uid'] }
  let(:client_uid) { properties['client_uid'] }
  let(:client_urn) { properties['client_uid'].split('/').last }
  let(:urn) { properties['urn'] }
  let(:latitude) { properties['latitude'] }
  let(:longitude) { properties['longitude'] }
  let(:properties) do
    JSON.parse(fixture('location-g5-cl-1soj9pe2-541-apartments.json'))['location']
  end
  let(:options) { {} }
  let(:updater) { described_class.new(properties, options) }

  shared_examples 'does not destroy existing clients and locations' do
    let!(:existing_client) { FactoryGirl.create(:client) }
    let!(:existing_location) { FactoryGirl.create(:location, client_uid: existing_client.uid) }

    it 'does not destroy the existing client or location' do
      expect(G5Updatable::Client.find(existing_client.id)).to_not be_nil
      expect(G5Updatable::Location.find(existing_location.id)).to_not be_nil
    end
  end

  describe "#update" do
    subject { G5Updatable::Location.last }

    context "with no existing Location records" do
      before { updater.update }

      it "creates a Location" do
        expect(G5Updatable::Location.count).to eq(1)
      end


      it 'redacts amenities key' do
        expect(subject.properties.keys.collect(&:to_sym)).to eq(properties.keys.collect(&:to_sym) - [:amenities])
      end

      its(:uid) { is_expected.to eq(uid) }
      its(:urn) { is_expected.to eq(urn) }
      its(:client_uid) { is_expected.to eq(client_uid) }
      its(:client_urn) { is_expected.to eq(client_urn
                                        ) }
      its(:name) { is_expected.to eq(name) }
      its(:latitude) { is_expected.to eq(latitude) }
      its(:longitude) { is_expected.to eq(longitude) }

      it 'sets name field so that consumers can easily sort by name' do
        subject
        expect(G5Updatable::Location.find_by_name(name)).to eq(subject)
      end

      it 'creates amenities' do
        subject
        expect(subject.hub_amenities.count).to eq(2)
        expect(subject.hub_amenities.collect(&:name)).to match_array(['Power', 'WIFI'])
        expect(subject.hub_amenities.collect(&:icon)).to match_array(%w(fa-electric-outlet fa-wifi))
        expect(subject.hub_amenities.first.external_updated_at).to be_present
        expect(subject.hub_amenities.first.external_created_at).to be_present
      end

      it_behaves_like 'does not destroy existing clients and locations'
    end


    context 'with an existing Location record' do
      let(:old_amenity) { create(:hub_amenity) }
      before do
        location = FactoryGirl.create(:location, uid: 'old', urn: urn, client_uid: client_urn)
        location.hub_amenities << old_amenity
        updater.update
      end

      it 'does not create a new Location' do
        expect(G5Updatable::Location.count).to eq(1)
      end

      its(:urn) { is_expected.to eq(urn) }
      its(:name) { is_expected.to eq name }

      it 'updates amenities' do
        expect(subject.hub_amenities.collect(&:name)).to match_array(['Power', 'WIFI'])
      end

      it 'sets flat amenities for easier exclusive querying' do
        expect(subject.flat_amenity_names).to eq('|Power|WIFI|')
      end

      it_behaves_like 'does not destroy existing clients and locations'

      context 'destroy_orphaned_locations set to false' do
        before do
          expect(updater).to_not receive :destroy_orphaned_locations!
        end

        it "does not create a new Location" do
          expect(G5Updatable::Location.count).to eq(1)
        end

        its(:urn) { is_expected.to eq(urn) }
        its(:name) { is_expected.to eq name }

        it_behaves_like 'does not destroy existing clients and locations'
      end
    end

    context "with an existing identical Location record" do
      before do
        FactoryGirl.create(:location, uid: uid, urn: urn, client_uid: "client_uid", name: name, properties: properties, updated_at: 1.hour.ago)
        @original_updated_at = subject.updated_at
        updater.update
      end

      it "updates its timestamp" do
        expect(subject.reload.updated_at).to be > @original_updated_at
      end

      it_behaves_like 'does not destroy existing clients and locations'

    end

    context "with no locations" do
      let(:g5_locations) { [] }
      let(:options) { {destroy_orphaned_locations: true} }

      before do
        FactoryGirl.create(:location, uid: uid, urn: "another_urn", client_urn: client_urn, client_uid: "client_uid", name: name, properties: properties)
        FactoryGirl.create(:location, uid: uid, urn: "some_urn", client_uid: "another_client_uid", name: name, properties: properties)
        updater.update
      end

      it "deletes locations" do
        expect(G5Updatable::Location.by_client_uid("client_uid").count).to eq(0)
        expect(G5Updatable::Location.by_client_uid("another_client_uid").count).to eq(1)
      end
    end

    context "with an orphaned location" do
      let(:options) { {destroy_orphaned_locations: true} }
      before do
        FactoryGirl.create(:location, uid: "another_uid", urn: "dead_urn", client_urn: client_urn)
        updater.update
      end

      it "deletes locations" do
        expect(G5Updatable::Location.by_urn(urn).by_client_urn(client_urn).count).to eq(1)
        expect(G5Updatable::Location.by_urn("dead_urn").by_client_urn(client_urn).count).to eq(0)
      end
    end

    context "callbacks" do
      before do
        FactoryGirl.create(:location, uid: 'old', urn: urn)
        @update_callback_called = false
        @create_callback_called = false
        described_class.on_update { |_location| @update_callback_called = true }
        described_class.on_create { |_location| @create_callback_called = true }
      end

      describe "update callbacks" do
        it "calls update callbacks passing the location that was updated" do
          expect(@update_callback_called).to be false
          updater.update
          expect(@update_callback_called).to be true
        end
      end

      describe "create callbacks" do
        it "skips create callbacks when the location already exists" do
          updater.update
          expect(@create_callback_called).to be false
        end

        context 'with some new locations' do
          let(:new_properties) do
            JSON.parse(fixture('client-g5-c-1soj8m6e-g5-multifamily.json'))['client']['locations']
          end
          it "calls create callbacks for a new location" do
            described_class.new(new_properties, options).update
            expect(@create_callback_called).to be true
          end
        end
      end
    end
  end

  describe ".on_update and .on_update_callbacks" do
    before do
      described_class.on_update_callbacks = []
    end

    it "adds to the #update callbacks" do
      callback_1 = -> (location, anything) {}
      callback_2 = -> (location, anything) {}
      described_class.on_update(&callback_1)
      described_class.on_update(&callback_2)

      expect(described_class.on_update_callbacks).
          to eq([callback_1, callback_2])
    end

    it "passes the location into each callback" do
      accumulator = []
      callback    = -> (location, anything) { accumulator << location.id }
      described_class.on_update(&callback)

      updater.update
      location = G5Updatable::Location.first

      expect(accumulator).to include(location.id)
    end
  end

  describe ".on_update_callbacks" do
    it "defaults to an empty array" do
      described_class.on_update_callbacks = nil
      expect(described_class.on_update_callbacks).to be_empty
    end
  end

  describe ".on_create and .on_create_callbacks" do
    before do
      described_class.on_create_callbacks = []
    end

    it "adds to the #update callbacks" do
      callback_1 = -> (location) {}
      callback_2 = -> (location) {}
      described_class.on_create(&callback_1)
      described_class.on_create(&callback_2)

      expect(described_class.on_create_callbacks).
          to eq([callback_1, callback_2])
    end

    it "passes the location into each callback" do
      accumulator = []
      callback    = -> (location) { accumulator << location.id }
      described_class.on_create(&callback)

      updater.update
      location = G5Updatable::Location.first

      expect(accumulator).to include(location.id)
    end
  end

  describe ".on_create_callbacks" do
    it "defaults to an empty array" do
      described_class.on_create_callbacks = nil
      expect(described_class.on_create_callbacks).to be_empty
    end
  end

end