require 'spec_helper'
require_db 'db_layer'
require_db 'grid'
require_db 'position/point'
require_intelligence 'processor'

module RCS
module Intelligence

describe Processor do

  enable_license
  silence_alerts

  it 'should use the Tracer module' do
    described_class.should respond_to :trace
  end

  before { Entity.any_instance.stub(:fetch_address) }

  describe '#process' do
    let! (:target) { factory_create(:target) }
    let! (:entity) { factory_create(:target_entity, target: target) }

    context 'the item is an aggregate' do

      let!(:aggregate) do
        factory_create(:aggregate, target: target, type: :sms, count: 1, data: {'peer' => 'harrenhal', 'versus' => :in})
      end

      let!(:queued_item) { IntelligenceQueue.create! target_id: target.id, type: :aggregate, ident: aggregate.id }

      it 'call #process_aggregate' do
        described_class.should_receive(:process_aggregate).with(entity, aggregate)
        described_class.process queued_item
      end
    end

    context 'the item is an evidence' do

      let!(:evidence) { factory_create(:evidence, target: target, type: 'camera') }

      let!(:queued_item) { IntelligenceQueue.create! target_id: target.id, type: :evidence, ident: evidence.id }

      it 'call #process_evidence' do
        described_class.should_receive(:process_evidence)
        described_class.process queued_item
      end
    end
  end


  describe '#run' do
    let(:queue_entry) { [:first_item, :second_item] }

    before { described_class.stub(:sleep).and_return :sleeping }
    before { described_class.stub(:loop).and_yield }

    context 'the IntelligenceQueue is not empty' do
      before { IntelligenceQueue.stub(:get_queued).and_return queue_entry }

      it 'should process the first entry' do
        described_class.should_receive(:process).with :first_item
        described_class.run
      end
    end

    context 'the IntelligenceQueue is empty' do
      before { IntelligenceQueue.stub(:get_queued).and_return nil }

      it 'should wait a second' do
        described_class.should_not_receive :process
        described_class.should_receive(:sleep).with 1
        described_class.run
      end
    end
  end


  describe '#process_evidence' do
    let(:target) { factory_create :target }
    let(:entity) { factory_create :target_entity, target: target }
    let(:evidence) { factory_create :addressbook_evidence, target: target }

    context 'the type of the evidence is "addressbook"' do
      # before { evidence.stub(:type).and_return 'addressbook' }
      before { Accounts.stub(:add_handle).and_return nil }

      context 'the license is invalid' do
        before { described_class.stub(:check_intelligence_license).and_return false }

        it 'should not create any link' do
          Ghost.should_not_receive :create_and_link_entity
          described_class.process_evidence entity, evidence
        end
      end

      context 'the license is valid' do

        it 'should create a link' do
          Accounts.stub :handle_attributes
          Ghost.should_receive :create_and_link_entity
          described_class.process_evidence entity, evidence
        end
      end
    end
  end

  describe '#process_aggregate' do
    let!(:aggregate_class) { Aggregate.target('testtarget') }
    let!(:aggregate_name) { "aggregate.testtarget" }
    let!(:operation_x) { Item.create!(name: 'test-operation-x', _kind: 'operation', path: [], stat: ::Stat.new) }
    let!(:operation_y) { Item.create!(name: 'test-operation-y', _kind: 'operation', path: [], stat: ::Stat.new) }
    let!(:number_of_alice) { '00112345' }
    let!(:number_of_bob) { '00145678' }

    context 'given an aggregate of type "position"' do
      let(:position_aggregate_of_bob) { aggregate_class.create!(day: Time.now.strftime('%Y%m%d'), type: :position, aid: 'agent_id') }
      let(:target) { factory_create(:target, operation: operation_x) }
      let(:entity) { factory_create(:target_entity, target: target) }

      it 'calls #process_position_aggregate' do
        described_class.should_receive :process_position_aggregate
        described_class.process_aggregate entity, position_aggregate_of_bob
      end
    end

    context 'given a aggregate of type "sms"' do
      let!(:sms_aggregate_of_bob) { aggregate_class.create!(day: Time.now.strftime('%Y%m%d'), type: :sms, aid: 'agent_id', count: 3,
        data: {'peer' => number_of_alice, 'versus' => :in, 'sender' => number_of_bob}) }

      # Create Alice (entity) with an handle (her phone number)
      let!(:entity_alice) do
        Item.create! name: 'alice', _kind: 'target', path: [operation_x._id], stat: ::Stat.new
        entity = Entity.where(name: 'alice').first
        entity.create_or_update_handle :phone, number_of_alice, number_of_alice.capitalize
        entity
      end

      context 'if an entity (same operation) can be linked to it' do
        let!(:entity_bob) do
          Item.create! name: 'bob', _kind: 'target', path: [operation_x._id], stat: ::Stat.new
          Entity.where(name: 'bob').first
        end

        it 'should create a link' do
          described_class.process_aggregate entity_bob, sms_aggregate_of_bob

          entity_alice.reload
          expect(entity_alice.linked_to?(entity_bob)).to be_truthy
        end

        context 'the "info" attribute of the created link' do

          let :created_link do
            described_class.process_aggregate entity_bob, sms_aggregate_of_bob
            entity_alice.reload.links.first
          end

          it 'contains the handles of the two linked entities' do
            created_link
            expect(created_link.info.values.flatten).to include number_of_bob
            expect(created_link.info.values.flatten).to include number_of_alice
          end

          # this situation should not be happen in version 9.0.0
          context 'when the aggregate does not have a "sender" attribute' do

            before do
              sms_aggregate_of_bob.data['sender'] = nil
              sms_aggregate_of_bob.save!
              created_link
            end

            it 'contains the handle of other entity' do
              expect(created_link.info.values.flatten).to include number_of_alice
            end
          end
        end
      end

      context 'if an entity (another operation) can be linked to it' do
        let!(:entity_bob) do
          Item.create!(name: 'test-target-b', _kind: 'target', path: [operation_y._id], stat: ::Stat.new)
          Entity.where(name: 'test-target-b').first
        end

        it 'should create a link' do
          RCS::DB::LinkManager.instance.should_receive :add_link
          described_class.process_aggregate entity_bob, sms_aggregate_of_bob
        end
      end

      context 'given an a target entity and a ghost entity linked to it' do

        let!(:operation) { factory_create :operation }
        let!(:target) { factory_create :target, operation: operation }
        let!(:target_entity) { factory_create :target_entity, target: target }
        let!(:ghost_entity) do
          e = factory_create :ghost_entity, operation: operation
          factory_create :entity_handle, entity: e, type: :phone, handle: '0010012'
          RCS::DB::LinkManager.instance.add_link(from: target_entity, to: e, level: :ghost, type: :know, versus: :out, info: '0010012')
          e
        end

        before do
          expect(target_entity.linked_to?(ghost_entity, type: :know, level: :ghost)).to be_truthy
        end

        context 'when a peer aggregate (communication beetwen the target and the ghost entity) arrives' do

          let!(:peer_aggregate) do
            factory_create :aggregate, target: target, type: :sms, count: 3, data: {'peer' => '0010012', 'versus' => :out, 'sender' => 'numer_of_target'}
          end

          it 'changes the level and the type of the link beetwen the two entities' do
            described_class.process_aggregate target_entity, peer_aggregate
            target_entity.reload
            ghost_entity.reload

            expect(ghost_entity.level).to eql :ghost
            expect(target_entity.linked_to?(ghost_entity, type: :peer, level: :ghost)).to be_truthy
          end
        end
      end
    end
  end

  describe '#process_position_aggregate' do

    def create_position_aggregate_for target, params
      data = {'position' => [params[:lon], params[:lat]], 'radius' => params[:rad]}
      info = [{'start' => params[:start], 'end' => params[:end]}]
      Aggregate.target(target).create! type: :position, info: info, data: data,  aid: 'agent_id', count: 1, day: '20130101'
    end

    let!(:operation) { Item.create!(name: 'opx', _kind: 'operation', path: [], stat: ::Stat.new) }

    let!(:target_bob) { Item.create! name: 'bob', _kind: 'target', path: [operation.id], stat: ::Stat.new }
    let!(:target_alice) { Item.create! name: 'alice', _kind: 'target', path: [operation.id], stat: ::Stat.new }
    let!(:target_eve) { Item.create! name: 'eve', _kind: 'target', path: [operation.id], stat: ::Stat.new }

    let!(:bob) { Entity.where(name: 'bob').first }
    let!(:alice) { Entity.where(name: 'alice').first }
    let!(:eve) { Entity.where(name: 'eve').first }

    let(:midday) { Time.new 2013, 01, 01, 12, 00, 00 }
    let(:london_eye) { [51.503894, 0.119390] } #lat, lon
    let(:near_london_eye) { [51.50391, 0.11961] } #lat, lon

    before { Entity.create_indexes }

    context 'when two target entities have been in the same place at the same time' do

      let!(:bob_position) { create_position_aggregate_for target_bob, lat: london_eye[0], lon: london_eye[1], rad: 10, start: midday, end: midday + 40.minutes }
      let!(:alice_position) { create_position_aggregate_for target_alice, lat: london_eye[0], lon: london_eye[1], rad: 12, start: midday, end: midday + 42.minutes }
      let!(:eve_position) { create_position_aggregate_for target_eve, lat: 10, lon: 10, rad: 1, start: 1, end: 2 }

      it 'creates a position entity for that place' do
        described_class.process_position_aggregate bob, bob_position

        expect(Entity.positions.count).to eql 1

        position_entity = Entity.positions.first

        expect(position_entity.type).to eql :position
        expect(position_entity.level).to eql :automatic
        expect(position_entity.path).to eql [bob.path.first]
      end

      it 'links the two target entities to the position entity' do
        described_class.process_position_aggregate bob, bob_position
        position_entity = Entity.positions.first

        expect(position_entity.linked_to? bob.reload).to be_truthy
        expect(position_entity.linked_to? alice.reload).to be_truthy
      end
    end

    context 'when a position entity already exists' do

      let!(:bob_position) { create_position_aggregate_for target_bob, lat: london_eye[0], lon: london_eye[1], rad: 10, start: midday, end: midday + 40.minutes }
      let!(:alice_position) { create_position_aggregate_for target_alice, lat: near_london_eye[0], lon: near_london_eye[1], rad: 12, start: midday, end: midday + 42.minutes }
      let!(:eve_position) { create_position_aggregate_for target_eve, lat: near_london_eye[0], lon: near_london_eye[1], rad: 15, start: midday, end: midday + 41.minutes }
      let!(:eve_position2) { create_position_aggregate_for target_eve, lat: 10, lon: 10, rad: 10, start: midday, end: midday + 41.minutes }

      let!(:existing_position) do
        Entity.create! type: :position, path: [operation.id], position: london_eye.reverse, level: :automatic, name: 'London eye'
      end

      it 'does not create any new position entity' do
        expect {
          described_class.process_position_aggregate bob, bob_position
          described_class.process_position_aggregate alice, alice_position
        }.not_to change(Entity.positions, :count)
      end

      # @note: the magic is in an Entity's callback for the create event
      it 'links all the target entities that have been there to that position' do
        described_class.process_position_aggregate eve, eve_position

        existing_position.reload
        expect(existing_position.linked_to? bob.reload).to be_truthy
        expect(existing_position.linked_to? alice.reload).to be_truthy
        expect(existing_position.linked_to? eve.reload).to be_truthy
      end
    end
  end
end

end
end
