# Copyright 2016 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

from google.cloud.exceptions import BadRequest
from google.cloud.exceptions import InternalServerError
from google.cloud.exceptions import NotFound
from google.cloud.exceptions import ServiceUnavailable
from google.cloud import monitoring

from test_utils.retry import RetryErrors
from test_utils.retry import RetryResult
from test_utils.system import unique_resource_id

retry_404 = RetryErrors(NotFound, max_tries=5)
retry_404_500 = RetryErrors((NotFound, InternalServerError))
retry_500 = RetryErrors(InternalServerError)
retry_503 = RetryErrors(ServiceUnavailable)


class TestMonitoring(unittest.TestCase):

    def test_fetch_metric_descriptor(self):
        METRIC_TYPE = (
            'pubsub.googleapis.com/topic/send_message_operation_count')
        METRIC_KIND = monitoring.MetricKind.DELTA
        VALUE_TYPE = monitoring.ValueType.INT64

        client = monitoring.Client()
        descriptor = client.fetch_metric_descriptor(METRIC_TYPE)

        expected_name = 'projects/{project}/metricDescriptors/{type}'.format(
            project=client.project,
            type=METRIC_TYPE,
        )
        self.assertEqual(descriptor.name, expected_name)
        self.assertEqual(descriptor.type, METRIC_TYPE)
        self.assertEqual(descriptor.metric_kind, METRIC_KIND)
        self.assertEqual(descriptor.value_type, VALUE_TYPE)
        self.assertTrue(descriptor.description)

        self.assertTrue(descriptor.labels)
        for label in descriptor.labels:
            self.assertTrue(label.key)
            self.assertTrue(label.value_type)
            self.assertTrue(label.description)

    def test_list_metric_descriptors(self):
        METRIC_TYPE = (
            'pubsub.googleapis.com/topic/send_message_operation_count')
        METRIC_KIND = monitoring.MetricKind.DELTA
        VALUE_TYPE = monitoring.ValueType.INT64

        client = monitoring.Client()

        descriptor = None
        for item in client.list_metric_descriptors():
            if item.type == METRIC_TYPE:
                descriptor = item

        self.assertIsNotNone(descriptor)

        expected_name = 'projects/{project}/{what}/{type}'.format(
            project=client.project,
            what='metricDescriptors',
            type=METRIC_TYPE,
        )
        self.assertEqual(descriptor.name, expected_name)
        self.assertEqual(descriptor.type, METRIC_TYPE)
        self.assertEqual(descriptor.metric_kind, METRIC_KIND)
        self.assertEqual(descriptor.value_type, VALUE_TYPE)
        self.assertTrue(descriptor.description)

        self.assertTrue(descriptor.labels)
        for label in descriptor.labels:
            self.assertTrue(label.key)
            self.assertTrue(label.value_type)
            self.assertTrue(label.description)

    def test_list_metric_descriptors_filtered(self):
        client = monitoring.Client()

        PREFIX = 'compute.googleapis.com/'
        descriptors = client.list_metric_descriptors(type_prefix=PREFIX)

        # There are currently 18 types with this prefix, but that may change.
        self.assertGreater(len(descriptors), 10)

        for descriptor in descriptors:
            self.assertTrue(descriptor.type.startswith(PREFIX))

    def test_fetch_resource_descriptor(self):
        RESOURCE_TYPE = 'pubsub_topic'

        client = monitoring.Client()
        descriptor = client.fetch_resource_descriptor(RESOURCE_TYPE)

        expected_name = 'projects/{project}/{what}/{type}'.format(
            project=client.project,
            what='monitoredResourceDescriptors',
            type=RESOURCE_TYPE,
        )
        self.assertEqual(descriptor.name, expected_name)
        self.assertEqual(descriptor.type, RESOURCE_TYPE)
        self.assertTrue(descriptor.display_name)
        self.assertTrue(descriptor.description)

        self.assertTrue(descriptor.labels)
        for label in descriptor.labels:
            self.assertTrue(label.key)
            self.assertTrue(label.value_type)
            self.assertTrue(label.description)

    def test_list_resource_descriptors(self):
        RESOURCE_TYPE = 'pubsub_topic'

        client = monitoring.Client()

        descriptor = None
        for item in client.list_resource_descriptors():
            if item.type == RESOURCE_TYPE:
                descriptor = item

        self.assertIsNotNone(descriptor)

        expected_name = 'projects/{project}/{what}/{type}'.format(
            project=client.project,
            what='monitoredResourceDescriptors',
            type=RESOURCE_TYPE,
        )
        self.assertEqual(descriptor.name, expected_name)
        self.assertEqual(descriptor.type, RESOURCE_TYPE)
        self.assertTrue(descriptor.display_name)
        self.assertTrue(descriptor.description)

        self.assertTrue(descriptor.labels)
        for label in descriptor.labels:
            self.assertTrue(label.key)
            self.assertTrue(label.value_type)
            self.assertTrue(label.description)

    def test_query(self):
        METRIC_TYPE = (
            'pubsub.googleapis.com/topic/send_message_operation_count')
        client = monitoring.Client()
        query = client.query(METRIC_TYPE, hours=1)
        # There may be no data, but we can ask anyway.
        for _ in query:
            pass    # Not necessarily reached.

    def test_create_and_delete_metric_descriptor(self):
        METRIC_TYPE = ('custom.googleapis.com/tmp/system_test_example' +
                       unique_resource_id())
        METRIC_KIND = monitoring.MetricKind.GAUGE
        VALUE_TYPE = monitoring.ValueType.DOUBLE
        DESCRIPTION = 'System test example -- DELETE ME!'

        client = monitoring.Client()
        descriptor = client.metric_descriptor(
            METRIC_TYPE,
            metric_kind=METRIC_KIND,
            value_type=VALUE_TYPE,
            description=DESCRIPTION,
        )

        retry_500(descriptor.create)()
        retry_404_500(descriptor.delete)()

    @RetryErrors(exception=BadRequest, max_tries=2)
    def test_write_point(self):
        METRIC_TYPE = ('custom.googleapis.com/tmp/system_test_example' +
                       unique_resource_id())
        METRIC_KIND = monitoring.MetricKind.GAUGE
        VALUE_TYPE = monitoring.ValueType.DOUBLE
        DESCRIPTION = 'System test example -- DELETE ME!'
        VALUE = 3.14

        client = monitoring.Client()
        descriptor = client.metric_descriptor(
            METRIC_TYPE,
            metric_kind=METRIC_KIND,
            value_type=VALUE_TYPE,
            description=DESCRIPTION,
        )

        descriptor.create()

        metric = client.metric(METRIC_TYPE, {})
        resource = client.resource('global', {})

        retry_500(client.write_point)(metric, resource, VALUE)

        def _query_timeseries_with_retries():
            MAX_RETRIES = 6

            def _has_timeseries(result):
                return len(list(result)) > 0

            retry_result = RetryResult(
                _has_timeseries,
                max_tries=MAX_RETRIES,
                backoff=3)(client.query)
            return RetryErrors(
                BadRequest,
                max_tries=MAX_RETRIES,
                backoff=3)(retry_result)

        query = _query_timeseries_with_retries()(METRIC_TYPE, minutes=5)
        timeseries_list = list(query)
        self.assertEqual(len(timeseries_list), 1)
        timeseries = timeseries_list[0]
        self.assertEqual(timeseries.metric, metric)
        # project_id label only exists on output.
        del timeseries.resource.labels['project_id']
        self.assertEqual(timeseries.resource, resource)

        descriptor.delete()

        with self.assertRaises(NotFound):
            descriptor.delete()


class TestMonitoringGroups(unittest.TestCase):

    def setUp(self):
        self.to_delete = []
        self.DISPLAY_NAME = 'Testing: New group'
        self.FILTER = 'resource.type = "gce_instance"'
        self.IS_CLUSTER = True

    def tearDown(self):
        for group in self.to_delete:
            retry_404(group.delete)()

    def test_create_group(self):
        client = monitoring.Client()
        group = client.group(
            display_name=self.DISPLAY_NAME,
            filter_string=self.FILTER,
            is_cluster=self.IS_CLUSTER,
        )

        retry_503(group.create)()
        self.to_delete.append(group)

        self.assertTrue(group.exists())

    def test_list_groups(self):
        client = monitoring.Client()
        new_group = client.group(
            display_name=self.DISPLAY_NAME,
            filter_string=self.FILTER,
            is_cluster=self.IS_CLUSTER,
        )
        before_groups = client.list_groups()
        before_names = set(group.name for group in before_groups)

        retry_503(new_group.create)()
        self.to_delete.append(new_group)

        self.assertTrue(new_group.exists())
        after_groups = client.list_groups()
        after_names = set(group.name for group in after_groups)
        self.assertEqual(after_names - before_names,
                         set([new_group.name]))

    def test_reload_group(self):
        client = monitoring.Client()
        group = client.group(
            display_name=self.DISPLAY_NAME,
            filter_string=self.FILTER,
            is_cluster=self.IS_CLUSTER,
        )

        retry_503(group.create)()
        self.to_delete.append(group)

        group.filter = 'resource.type = "aws_ec2_instance"'
        group.display_name = 'locally changed name'
        group.reload()
        self.assertEqual(group.filter, self.FILTER)
        self.assertEqual(group.display_name, self.DISPLAY_NAME)

    def test_update_group(self):
        NEW_FILTER = 'resource.type = "aws_ec2_instance"'
        NEW_DISPLAY_NAME = 'updated'

        client = monitoring.Client()
        group = client.group(
            display_name=self.DISPLAY_NAME,
            filter_string=self.FILTER,
            is_cluster=self.IS_CLUSTER,
        )

        retry_503(group.create)()
        self.to_delete.append(group)

        group.filter = NEW_FILTER
        group.display_name = NEW_DISPLAY_NAME
        group.update()

        after = client.fetch_group(group.id)
        self.assertEqual(after.filter, NEW_FILTER)
        self.assertEqual(after.display_name, NEW_DISPLAY_NAME)

    def test_list_group_members(self):
        client = monitoring.Client()
        group = client.group(
            display_name=self.DISPLAY_NAME,
            filter_string=self.FILTER,
            is_cluster=self.IS_CLUSTER,
        )

        retry_503(group.create)()
        self.to_delete.append(group)

        for member in group.list_members():
            self.assertIsInstance(member, monitoring.Resource)

    def test_group_hierarchy(self):
        client = monitoring.Client()
        root_group = client.group(
            display_name='Testing: Root group',
            filter_string=self.FILTER,
        )

        retry_503(root_group.create)()
        self.to_delete.insert(0, root_group)

        middle_group = client.group(
            display_name='Testing: Middle group',
            filter_string=self.FILTER,
            parent_id=root_group.id,
        )

        retry_503(middle_group.create)()
        self.to_delete.insert(0, middle_group)

        leaf_group = client.group(
            display_name='Testing: Leaf group',
            filter_string=self.FILTER,
            parent_id=middle_group.id,
        )

        retry_503(leaf_group.create)()
        self.to_delete.insert(0, leaf_group)

        # Test for parent.
        actual_parent = middle_group.fetch_parent()
        self.assertTrue(actual_parent.name, root_group.name)

        # Test for children.
        actual_children = middle_group.list_children()
        children_names = [group.name for group in actual_children]
        self.assertEqual(children_names, [leaf_group.name])

        # Test for descendants.
        actual_descendants = root_group.list_descendants()
        descendant_names = {group.name for group in actual_descendants}
        self.assertEqual(descendant_names,
                         set([middle_group.name, leaf_group.name]))

        # Test for ancestors.
        actual_ancestors = leaf_group.list_ancestors()
        ancestor_names = [group.name for group in actual_ancestors]
        self.assertEqual(ancestor_names, [middle_group.name, root_group.name])
