class OneItemSpatialTestCase(StacBaseTestCase):
    def setUp(self):
        self.client = Client()
        client_login(self.client)
        self.factory = Factory()
        self.collection = self.factory.create_collection_sample().model
        self.items = self.factory.create_item_samples(
            ['item-switzerland-west'], self.collection, db_create=True)

    def test_single_item(self):
        collection_name = self.collection.name
        item_name = self.items[0].model.name
        response_item = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}")
        response_item_json = response_item.json()
        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        response_collection_json = response_collection.json()

        bbox_collection = response_collection_json['extent']['spatial'][
            'bbox'][0]
        bbox_items = response_item_json['bbox']

        self.assertEqual(bbox_items, bbox_collection)

    def test_no_items(self):
        collection_name = self.collection.name
        item_name = self.items[0].model.name
        # delete the item
        path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_name}'
        response = self.client.delete(path)
        self.assertStatusCode(200, response)

        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        response_collection_json = response_collection.json()

        bbox_collection = response_collection_json['extent']['spatial'][
            'bbox'][0]

        self.assertEqual(bbox_collection, [])
class TwoItemsSpatialTestCase(StacBaseTestCase):
    def setUp(self):
        self.client = Client()
        client_login(self.client)
        self.factory = Factory()
        self.collection = self.factory.create_collection_sample().model
        self.items = self.factory.create_item_samples(
            ['item-switzerland-west', 'item-switzerland-east'],
            self.collection,
            name=['item-switzerland-west', 'item-switzerland-east'],
            db_create=True,
        )

    def test_two_item_endpoint(self):
        collection_name = self.collection.name
        item_west = self.items[0].model.name
        response_item_west = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_west}")
        item_east = self.items[1].model.name
        response_item_east = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_east}")

        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        response_collection_json = response_collection.json()

        bbox_collection = response_collection.json(
        )['extent']['spatial']['bbox'][0]
        bbox_item_west = response_item_west.json()['bbox']
        bbox_item_east = response_item_east.json()['bbox']

        self.assertNotEqual(
            bbox_item_west,
            bbox_item_east,
            msg=
            'the bbox should not be the same. one should cover the east, the other the west'
        )
        self.assertNotEqual(
            bbox_item_west,
            bbox_collection,
            msg='the item bbox should be within the collection bbox')
        self.assertNotEqual(
            bbox_item_east,
            bbox_collection,
            msg='the item bbox should be within the collection bbox')

        polygon_west = Polygon.from_bbox(bbox_item_west)
        polygon_east = Polygon.from_bbox(bbox_item_east)
        union_polygon = Polygon.from_bbox(
            self._round_list(polygon_west.union(polygon_east).extent))

        collection_polygon = Polygon.from_bbox(
            self._round_list(bbox_collection))

        self.assertEqual(
            collection_polygon,
            union_polygon,
            msg=
            'the union of item east and item west should be identical with the collection'
        )

    def test_one_left_item(self):
        collection_name = self.collection.name
        item_west = self.items[0].model.name
        item_east = self.items[1].model.name

        # delete the eastern item
        path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_east}'
        response = self.client.delete(path)
        self.assertStatusCode(200, response)

        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        response_item_west = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_west}")
        bbox_item_west = response_item_west.json()['bbox']

        self.assertEqual(
            self._round_list(bbox_collection),
            self._round_list(bbox_item_west),
            msg='the item and the collection bbox should be congruent')

    def test_update_covering_item(self):
        collection_name = self.collection.name
        item_name = self.items[0].model.name
        sample = self.factory.create_item_sample(
            self.collection, sample='item-covers-switzerland', name=item_name)
        path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_name}'
        response = self.client.put(path,
                                   data=sample.get_json('put'),
                                   content_type="application/json")

        response_item = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}")
        bbox_item = response_item.json()['bbox']

        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        self.assertEqual(
            self._round_list(bbox_collection),
            self._round_list(bbox_item),
            msg='the item and the collection bbox should be congruent')

    def test_add_covering_item(self):
        collection_name = self.collection.name
        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection_ch = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        sample = self.factory.create_item_sample(
            self.collection, sample='item-covers-switzerland',
            db_create=True).model
        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection_covers_ch = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        self.assertNotEqual(
            self._round_list(bbox_collection_ch),
            self._round_list(bbox_collection_covers_ch),
            msg=
            'the bbox that covers switzerland should be different from the bbox of ch'
        )

        polygon_ch = Polygon.from_bbox(bbox_collection_ch)
        polygon_covers_ch = Polygon.from_bbox(bbox_collection_covers_ch)

        self.assertGreater(
            polygon_covers_ch.area,
            polygon_ch.area,
            msg=
            'the area of the polygon covering CH has to be bigger than the bbox CH'
        )

    def test_add_another_item(self):
        collection_name = self.collection.name
        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection_ch = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        sample = self.factory.create_item_sample(self.collection,
                                                 sample='item-paris',
                                                 db_create=True).model
        response_collection = self.client.get(
            f"/{STAC_BASE_V}/collections/{collection_name}")
        bbox_collection_paris = response_collection.json(
        )['extent']['spatial']['bbox'][0]

        self.assertNotEqual(self._round_list(bbox_collection_ch),
                            self._round_list(bbox_collection_paris),
                            msg='the bbox should have changed meanwhile')

        polygon_ch = Polygon.from_bbox(bbox_collection_ch)
        polygon_paris = Polygon.from_bbox(bbox_collection_paris)

        self.assertGreater(
            polygon_paris.area,
            polygon_ch.area,
            msg=
            'the area of the bbox up to Paris has to be bigger than the bbox of Switzerland'
        )

    def _round_list(self, unrounded_list):
        '''round a list of numbers

        Args:
            unrounded_list: list(float)
        Returns:
            list
                A list of rounded numbers
        '''
        rounded_list = [round(i, 5) for i in unrounded_list]
        return rounded_list
class CollectionsCreateEndpointTestCase(StacBaseTestCase):

    def setUp(self):  # pylint: disable=invalid-name
        self.client = Client()
        client_login(self.client)
        self.factory = Factory()
        self.collection = self.factory.create_collection_sample()
        self.maxDiff = None  # pylint: disable=invalid-name

    def test_collection_upsert_create(self):
        sample = self.factory.create_collection_sample(sample='collection-2')

        # the dataset to update does not exist yet
        response = self.client.put(
            f"/{STAC_BASE_V}/collections/{sample['name']}",
            data=sample.get_json('put'),
            content_type='application/json'
        )
        self.assertStatusCode(201, response)

        self.check_stac_collection(sample.json, response.json())

    def test_invalid_collections_create(self):
        # the dataset already exists in the database
        collection = self.factory.create_collection_sample(sample='collection-invalid')

        response = self.client.put(
            f"/{STAC_BASE_V}/collections/{collection['name']}",
            data=collection.get_json('put'),
            content_type='application/json'
        )
        self.assertStatusCode(400, response)
        self.assertEqual({'license': ['Not a valid string.']},
                         response.json()['description'],
                         msg='Unexpected error message')

    def test_collections_min_mandatory_create(self):
        # a post with the absolute valid minimum
        collection = self.factory.create_collection_sample(required_only=True)

        path = f"/{STAC_BASE_V}/collections/{collection['name']}"
        response = self.client.put(
            path, data=collection.get_json('put'), content_type='application/json'
        )
        response_json = response.json()
        logger.debug(response_json)
        self.assertStatusCode(201, response)
        self.check_header_location(f'{path}', response)
        self.assertNotIn('title', response_json.keys())  # key does not exist
        self.assertNotIn('providers', response_json.keys())  # key does not exist
        self.check_stac_collection(collection.json, response_json)

    def test_collections_less_than_mandatory_create(self):
        # a post with the absolute valid minimum
        collection = self.factory.create_collection_sample(
            sample='collection-missing-mandatory-fields'
        )

        response = self.client.put(
            f"/{STAC_BASE_V}/collections/{collection['name']}",
            data=collection.get_json('put'),
            content_type='application/json'
        )
        self.assertStatusCode(400, response)
        self.assertEqual(
            {
                'description': ['This field is required.'],
                'license': ['This field is required.'],
            },
            response.json()['description'],
            msg='Unexpected error message',
        )

    def test_collections_create_unpublished(self):
        published_collection = self.factory.create_collection_sample(db_create=True)
        published_items = self.factory.create_item_samples(
            2, collection=published_collection.model, name=['item-1-1', 'item-1-2'], db_create=True
        )

        collection_sample = self.factory.create_collection_sample(published=False)

        path = f"/{STAC_BASE_V}/collections/{collection_sample['name']}"
        response = self.client.put(
            path, data=collection_sample.get_json('put'), content_type='application/json'
        )
        self.assertStatusCode(201, response)
        self.check_header_location(f'{path}', response)
        self.assertNotIn(
            'published', response.json(), msg="'published' flag should not be seen in answer"
        )
        collection = Collection.objects.get(name=collection_sample['name'])
        self.assertFalse(
            collection.published, msg='Collection marked as published when it shouldn\'t'
        )

        # verify that the collection is not found in the collection list
        response = self.client.get(f"/{STAC_BASE_V}/collections")
        self.assertStatusCode(200, response)
        self.assertEqual(
            len(response.json()['collections']),
            1,
            msg="The un published collection is part of the collection list"
        )
        self.assertEqual(response.json()['collections'][0]['id'], published_collection['name'])

        # add some items to the collection
        items = self.factory.create_item_samples(
            2, collection=collection, name=['item-2-1', 'item-2-2'], db_create=True
        )

        # Check that those items are not found in the search endpoint
        response = self.client.get(f'/{STAC_BASE_V}/search')
        self.assertStatusCode(200, response)
        self.assertEqual(
            len(response.json()['features']),
            2,
            msg="Too many items found, probably the unpublished are also returned"
        )
        for i, item in enumerate(response.json()['features']):
            self.assertEqual(item['id'], published_items[i]['name'])

        # Publish the collection
        response = self.client.patch(
            f"/{STAC_BASE_V}/collections/{collection.name}",
            data={'published': True},
            content_type='application/json'
        )
        self.assertStatusCode(200, response)

        # verify that now the collection can be seen
        response = self.client.get(f"/{STAC_BASE_V}/collections")
        self.assertStatusCode(200, response)
        self.assertEqual(len(response.json()['collections']), 2, msg="No enough collections found")
        self.assertEqual(response.json()['collections'][0]['id'], published_collection.json['id'])
        self.assertEqual(response.json()['collections'][1]['id'], collection.name)

        # Check that the items are found in the search endpoint
        response = self.client.get(f'/{STAC_BASE_V}/search')
        self.assertStatusCode(200, response)
        self.assertEqual(len(response.json()['features']), 4, msg="Not all published items found")

    def test_collection_atomic_upsert_create_500(self):
        sample = self.factory.create_collection_sample(sample='collection-2')

        # the dataset to update does not exist yet
        with self.settings(DEBUG_PROPAGATE_API_EXCEPTIONS=True), disableLogger('stac_api.apps'):
            response = self.client.put(
                reverse('test-collection-detail-http-500', args=[sample['name']]),
                data=sample.get_json('put'),
                content_type='application/json'
            )
        self.assertStatusCode(500, response)
        self.assertEqual(response.json()['description'], "AttributeError('test exception')")

        # Make sure that the ressource has not been created
        response = self.client.get(reverse('collection-detail', args=[sample['name']]))
        self.assertStatusCode(404, response)