class TestPointsOfInterest(unittest.TestCase):

    def setUp(self):
        self._mock_mongo = Mock()
        self._mock_elasticsearch = Mock()
        self._pois = PointsOfInterest('test', self._mock_mongo, self._mock_elasticsearch)
        self._mock_mongo.pois.find_one.return_value = None

    def test_add_adds_to_database(self):
        poi = PointOfInterest()
        self._pois.add_or_update(poi)
        self._mock_mongo.pois.insert.assert_called_once_with(poi._asdict())

    def test_add_indexes(self):
        poi = PointOfInterest()
        poi.slug = 'osm:N12345'
        poi.names = [LocalisedName(name='Test', lang='en')]
        poi.descriptions = [LocalisedName(name='Descriptions', lang='en')]
        poi.geography = Point(-1.6, 54.0)
        poi.identifiers = [Identifier(namespace='foo', value='bar')]

        self._pois.add_or_update(poi)

    def test_add_or_update_checks_for_uri_clashes_before_adding(self):
        self._mock_mongo.pois.find_one.return_value = {
            '_id': 'abcdef', 'sources': [Source(url='http://www.example.com', version=1, attribution='OSM')]
        }
        poi = PointOfInterest(slug='test:test')
        self._pois.add_or_update(poi)
        poi_dict = poi._asdict()
        poi_dict.update({'_id': 'abcdef'})
        self.assertFalse(self._mock_mongo.db.pois.insert.called)
        self._mock_mongo.pois.update.assert_called_once_with({'slug': 'test:test'}, poi_dict)

    def test_add_or_update_does_not_update_if_source_has_not_changed(self):
        self._mock_mongo.pois.find_one.return_value = {
            '_id': 'abcdef',
            'sources': [Source(url='http://www.example.com', version=1, attribution=None)._asdict()]
        }

        poi = PointOfInterest(
            slug='test:test', sources=[Source(url='http://www.example.com', version=1, attribution=None)]
        )
        self._pois.add_or_update(poi)

        self.assertFalse(self._mock_mongo.pois.insert.called)
        self.assertFalse(self._mock_mongo.pois.update.called)

    def test_fetch_by_uri_returns_point_of_interest(self):
        self._mock_mongo.pois.find_one.return_value = {'foo':'bar'}
        self.assertIsInstance(self._pois.select_by_slug('/'), PointOfInterest)

    def test_fetch_by_uri_looks_up_correct_item(self):
        slug = 'foo:bar'
        self._mock_mongo.pois.find_one.return_value = {'locality': 'Eccles'}
        poi = self._pois.select_by_slug(slug)
        self.assertEquals('Eccles', poi.locality)
        self._mock_mongo.pois.find_one.assert_called_once_with({'slug': slug})

    def test_fetch_by_uri_returns_none_when_nothing_found(self):
        self._mock_mongo.pois.find_one.return_value = None
        self.assertIsNone(self._pois.select_by_slug(''))

    def test_slug_is_indexed(self):
        self._mock_mongo.pois.ensure_index.assert_any_call('slug')

    def test_categories_are_indexed(self):
        self._mock_mongo.pois.ensure_index.assert_any_call('categories')

    def test_amenities_are_indexed(self):
        self._mock_mongo.pois.ensure_index.assert_any_call('amenities')

    def test_location_is_indexed(self):
        self._mock_mongo.pois.ensure_index.assert_any_call([('location', '2dsphere')])

    def test_count_nearby_category_returns_0_by_default(self):
        self._setup_results_with_count(0)
        self.assertEquals(0, self._pois.count_nearby_category(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_count_nearby_amenity_returns_0_by_default(self):
        self._setup_results_with_count(0)
        self.assertEquals(0, self._pois.count_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_count_from_search_results_for_categories_is_returned(self):
        self._setup_results_with_count(42)
        self.assertEquals(42, self._pois.count_nearby_category(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_count_from_search_results_for_amenities_is_returned(self):
        self._setup_results_with_count(42)
        self.assertEquals(42, self._pois.count_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_correct_categories_query_is_supplied_to_mongo(self):
        self._pois.count_nearby_category(Point(-1.6, 54.0), 'http://www.example.com')
        self._mock_mongo.pois.find.assert_called_once_with({
            'location': {'$near': {'$geometry': {'type': 'Point', 'coordinates': (-1.6, 54.0)}}},
            'categories': 'http://www.example.com'
        })

    def test_correct_amenities_query_is_supplied_to_mongo(self):
        self._pois.count_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com')
        self._mock_mongo.pois.find.assert_called_once_with({
            'location': {'$near': {'$geometry': {'type': 'Point', 'coordinates': (-1.6, 54.0)}}},
            'amenities': 'http://www.example.com'
        })

    def test_maxdistance_parameter_is_provided_when_radius_is(self):
        self._pois.count_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com', radius=1234)
        self._mock_mongo.pois.find.assert_called_once_with({
            'location': {'$near': {'$geometry': {'type': 'Point', 'coordinates': (-1.6, 54.0)}, '$maxDistance': 1234}},
            'amenities': 'http://www.example.com'
        })

    def test_search_nearby_categories_returns_list_by_default(self):
        self._mock_mongo.pois.find.return_value = []
        self.assertEquals([], self._pois.search_nearby_category(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_search_nearby_amenities_returns_list_by_default(self):
        self._mock_mongo.pois.find.return_value = []
        self.assertEquals([], self._pois.search_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com'))

    def test_returned_pois_are_returned_as_models_by_category(self):
        self._mock_mongo.pois.find.return_value = [{'slug': 'foo'}]
        points_of_interest = self._pois.search_nearby_category(Point(-1.6, 54.0), 'http://www.example.com')
        self.assertEquals(1, len(points_of_interest))
        self.assertEquals('foo', points_of_interest[0].slug)

    def test_returned_pois_are_returned_as_models_by_amenity(self):
        self._mock_mongo.pois.find.return_value = [{'slug': 'foo'}]
        points_of_interest = self._pois.search_nearby_amenity(Point(-1.6, 54.0), 'http://www.example.com')
        self.assertEquals(1, len(points_of_interest))
        self.assertEquals('foo', points_of_interest[0].slug)

    def test_adding_new_poi_adds_it_to_elasticsearch_index(self):
        poi = PointOfInterest(
            slug='test:test',
            names=LocalisedNames([LocalisedName(name="Hello world", lang="en-gb")]),
            identifiers=Identifiers([Identifier(value="id1", namespace="test")])
        )
        self._pois.add_or_update(poi)
        self._mock_elasticsearch.index.assert_called_once_with(
            'test',
            'poi',
            {
                'names': ['Hello world'],
                'identifiers': ['id1']
            },
            'test:test'
        )

    def test_searching_by_name_constructs_correct_query(self):
        self._mock_elasticsearch.search.return_value = {'hits': {'hits': []}}
        self._mock_mongo.pois.find.return_value = []
        self._pois.search_name("hello world")
        self._mock_elasticsearch.search.assert_called_once_with(
            {
                'query': {
                    'bool': {
                        'should': [
                            {'match': {'names': "hello world"}},
                            {'term': {'identifiers': "hello world"}}
                        ]
                    }
                }
            },
            index='test',
            doc_type='poi'
        )

    def test_search_results_are_fetched_from_mongo(self):
        self._mock_elasticsearch.search.return_value = {
            'hits': {
                'hits': [
                    {'_id': 'test:test1'},
                    {'_id': 'test:test2'}
                ]
            }
        }
        self._mock_mongo.pois.find.return_value = [
            PointOfInterest(slug='test:test2')._asdict(),
            PointOfInterest(slug='test:test1')._asdict()
        ]
        self._pois.search_name("hello world")
        self._mock_mongo.pois.find.assert_called_once_with({"$or": [{"slug": 'test:test1'}, {'slug': 'test:test2'}]})

    def test_pois_are_returned_from_search_by_name(self):
        self._mock_elasticsearch.search.return_value = {
            'hits': {
                'hits': [
                    {'_id': 'test:test1'},
                    {'_id': 'test:test2'}
                ]
            }
        }
        self._mock_mongo.pois.find.return_value = [
            PointOfInterest(slug='test:test2')._asdict(),
            PointOfInterest(slug='test:test1')._asdict()
        ]
        pois = self._pois.search_name("hello world")
        self.assertEquals('test:test1', pois[0].slug)
        self.assertEquals('test:test2', pois[1].slug)

    def test_when_no_results_mongo_is_not_called(self):
        self._mock_elasticsearch.search.return_value = {'hits': {'hits': []}}
        self._pois.search_name("hello world")
        self._mock_mongo.pois.assert_never_called()

    def _setup_results_with_count(self, count):
        mock_result = Mock()
        mock_result.count.return_value = count
        self._mock_mongo.pois.find.return_value = mock_result
 def setUp(self):
     self._mock_mongo = Mock()
     self._mock_elasticsearch = Mock()
     self._pois = PointsOfInterest('test', self._mock_mongo, self._mock_elasticsearch)
     self._mock_mongo.pois.find_one.return_value = None