# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
#
# 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 time

from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import constants
from monasca_tempest_tests.tests.api import helpers
from tempest.common.utils import data_utils
from tempest.lib import exceptions
from tempest import test

NUM_MEASUREMENTS = 50
ONE_SECOND = 1000


class TestMeasurements(base.BaseMonascaTest):

    @classmethod
    def resource_setup(cls):
        super(TestMeasurements, cls).resource_setup()

        start_timestamp = int(time.time() * 1000)
        start_time = str(helpers.timestamp_to_iso(start_timestamp))
        metrics = []
        name1 = data_utils.rand_name()
        name2 = data_utils.rand_name()
        cls._names_list = [name1, name2]
        key = data_utils.rand_name('key')
        value = data_utils.rand_name('value')
        cls._key = key
        cls._value = value
        cls._start_timestamp = start_timestamp

        for i in xrange(NUM_MEASUREMENTS):
            metric = helpers.create_metric(
                name=name1,
                timestamp=start_timestamp + (i * 10),
                value=i)
            metrics.append(metric)
        cls.monasca_client.create_metrics(metrics)

        # Create metric2 for test_list_measurements_with_dimensions
        metric2 = helpers.create_metric(
            name=name1, timestamp=start_timestamp + ONE_SECOND * 2,
            dimensions={key: value}, value=NUM_MEASUREMENTS)
        cls.monasca_client.create_metrics(metric2)

        # Create metric3 for test_list_measurements_with_offset_limit
        metric3 = [
            helpers.create_metric(
                name=name2, timestamp=start_timestamp + ONE_SECOND * 3,
                dimensions={'key1': 'value1', 'key2': 'value5', 'key3': 'value7'}),
            helpers.create_metric(
                name=name2, timestamp=start_timestamp + ONE_SECOND * 3 + 10,
                dimensions={'key1': 'value2', 'key2': 'value5', 'key3': 'value7'}),
            helpers.create_metric(
                name=name2, timestamp=start_timestamp + ONE_SECOND * 3 + 20,
                dimensions={'key1': 'value3', 'key2': 'value6', 'key3': 'value7'}),
            helpers.create_metric(
                name=name2, timestamp=start_timestamp + ONE_SECOND * 3 + 30,
                dimensions={'key1': 'value4', 'key2': 'value6', 'key3': 'value8'})
        ]
        cls.monasca_client.create_metrics(metric3)

        # Create metric3 for test_list_measurements_with_no_merge_metrics
        metric4 = helpers.create_metric(
            name=name1, timestamp=start_timestamp + ONE_SECOND * 4,
            dimensions={'key-1': 'value-1'},
            value=NUM_MEASUREMENTS + 1)
        cls.monasca_client.create_metrics(metric4)

        end_time = str(helpers.timestamp_to_iso(
            start_timestamp + NUM_MEASUREMENTS + ONE_SECOND * 5))
        queries = []
        queries.append('?name={}&start_time={}&end_time={}&merge_metrics=true'.
                       format(name1, start_time, end_time))
        queries.append('?name={}&start_time={}&end_time={}&merge_metrics=true'.
                       format(name2, start_time, end_time))

        for timer in xrange(constants.MAX_RETRIES):
            responses = map(cls.monasca_client.list_measurements, queries)
            resp_first = responses[0][0]
            response_body_first = responses[0][1]
            resp_second = responses[1][0]
            response_body_second = responses[1][1]
            if resp_first.status == 200 and resp_second.status == 200 \
                    and len(response_body_first['elements']) == 1 \
                    and len(response_body_second['elements']) == 1:
                len_meas_first = len(
                    response_body_first['elements'][0]['measurements'])
                len_meas_second = len(
                    response_body_second['elements'][0]['measurements'])
                if len_meas_first == NUM_MEASUREMENTS + 2 \
                        and len_meas_second == 4:
                    break
                else:
                    time.sleep(constants.RETRY_WAIT_SECS)
            else:
                time.sleep(constants.RETRY_WAIT_SECS)

        cls._start_time = start_time
        cls._end_time = end_time

    @test.attr(type="gate")
    def test_list_measurements(self):
        query_parms = '?name=' + str(self._names_list[0]) + \
                      '&merge_metrics=true' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self._verify_list_measurements(resp, response_body)
        elements = response_body['elements']
        self._verify_list_measurements_elements(
            elements=elements, test_key=None, test_value=None)
        measurements = elements[0]['measurements']
        self._verify_list_measurements_meas_len(
            measurements, test_len=NUM_MEASUREMENTS + 2)
        i = 0
        for measurement in measurements:
            self._verify_list_measurements_measurement(measurement, i)
            i += 1

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_no_start_time(self):
        query_parms = '?name=' + str(self._names_list[0])
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.list_measurements, query_parms)

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_no_name(self):
        query_parms = '?start_time=' + str(self._start_time) + '&end_time=' + \
                      str(self._end_time)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.list_measurements, query_parms)

    @test.attr(type="gate")
    def test_list_measurements_with_dimensions(self):
        query_parms = '?name=' + self._names_list[0] + '&start_time=' + \
                      str(self._start_time) + '&end_time=' + \
                      str(self._end_time) + '&dimensions=' + self._key + ':' \
                      + self._value
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self._verify_list_measurements(resp, response_body)
        elements = response_body['elements']
        self._verify_list_measurements_elements(
            elements=elements, test_key=None, test_value=None)
        measurements = elements[0]['measurements']
        self._verify_list_measurements_meas_len(measurements, 1)
        measurement = measurements[0]
        self._verify_list_measurements_measurement(
            measurement=measurement, test_value=NUM_MEASUREMENTS)

    @test.attr(type="gate")
    def test_list_measurements_with_endtime(self):
        time_iso = helpers.timestamp_to_iso(
            self._start_timestamp + ONE_SECOND * 2)
        query_parms = '?name=' + str(self._names_list[0]) + \
                      '&merge_metrics=true' \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(time_iso)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self._verify_list_measurements(resp, response_body)
        elements = response_body['elements']
        self._verify_list_measurements_elements(elements=elements,
                                                test_key=None, test_value=None)
        measurements = elements[0]['measurements']
        self._verify_list_measurements_meas_len(measurements=measurements,
                                                test_len=NUM_MEASUREMENTS)

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_endtime_equals_starttime(self):
        query_parms = '?name=' + str(self._names_list[0]) + \
                      '&merge_metrics=true' \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._start_time)
        self.assertRaises(exceptions.BadRequest,
                          self.monasca_client.list_measurements, query_parms)

    @test.attr(type="gate")
    def test_list_measurements_with_offset_limit(self):
        query_parms = '?name=' + str(self._names_list[1]) + \
                      '&merge_metrics=true&start_time=' + self._start_time + \
                      '&end_time=' + self._end_time
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self._verify_list_measurements(resp, response_body)
        elements = response_body['elements']
        self._verify_list_measurements_elements(elements=elements,
                                                test_key=None, test_value=None)
        measurements = elements[0]['measurements']
        self._verify_list_measurements_meas_len(measurements=measurements,
                                                test_len=4)

        for measurement_index in xrange(1, len(measurements) - 3):
            max_limit = len(measurements) - measurement_index

            # Get first offset from api
            query_parms = '?name=' + str(self._names_list[1]) + \
                          '&merge_metrics=true&start_time=' + measurements[measurement_index - 1][0] + \
                          '&end_time=' + self._end_time + \
                          '&limit=1'
            resp, response_body = self.monasca_client.list_measurements(query_parms)
            for link in response_body['links']:
                if link['rel'] == 'next':
                    next_link = link['href']
            if not next_link:
                self.fail("No next link returned with query parameters: {}".formet(query_parms))
            offset = helpers.get_query_param(next_link, "offset")

            first_index = measurement_index + 1

            for limit in xrange(1, max_limit):
                last_index = measurement_index + limit + 1
                expected_measurements = measurements[first_index:last_index]

                query_parms = '?name=' + str(self._names_list[1]) + \
                              '&merge_metrics=true&start_time=' + \
                              self._start_time + '&end_time=' + \
                              self._end_time + '&limit=' + str(limit) + \
                              '&offset=' + str(offset)

                resp, response_body = self.monasca_client.list_measurements(
                    query_parms)
                self._verify_list_measurements(resp, response_body)
                new_measurements = response_body['elements'][0]['measurements']

                self.assertEqual(limit, len(new_measurements))
                for i in xrange(len(expected_measurements)):
                    self.assertEqual(expected_measurements[i],
                                     new_measurements[i])

    @test.attr(type="gate")
    def test_list_measurements_with_merge_metrics(self):
        query_parms = '?name=' + str(self._names_list[0]) + \
                      '&merge_metrics=true' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self.assertEqual(200, resp.status)

    @test.attr(type="gate")
    def test_list_measurements_with_group_by_one(self):
        query_parms = '?name=' + str(self._names_list[1]) + \
                      '&group_by=key2' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self.assertEqual(200, resp.status)
        elements = response_body['elements']
        self.assertEqual(len(elements), 2)
        self._verify_list_measurements_elements(elements, None, None)
        for measurements in elements:
            self.assertEqual(1, len(measurements['dimensions'].keys()))
            self.assertEqual([u'key2'], measurements['dimensions'].keys())

    @test.attr(type="gate")
    def test_list_measurements_with_group_by_multiple(self):
        query_parms = '?name=' + str(self._names_list[1]) + \
                      '&group_by=key2,key3' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self.assertEqual(200, resp.status)
        elements = response_body['elements']
        self.assertEqual(len(elements), 3)
        self._verify_list_measurements_elements(elements, None, None)
        for measurements in elements:
            self.assertEqual(2, len(measurements['dimensions'].keys()))
            self.assertEqual({u'key2', u'key3'}, set(measurements['dimensions'].keys()))

    @test.attr(type="gate")
    def test_list_measurements_with_group_by_all(self):
        query_parms = '?name=' + str(self._names_list[1]) + \
                      '&group_by=*' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self.assertEqual(200, resp.status)
        elements = response_body['elements']
        self.assertEqual(len(elements), 4)
        self._verify_list_measurements_elements(elements, None, None)

    @test.attr(type="gate")
    def test_list_measurements_with_group_by_and_merge(self):
        query_parms = '?name=' + str(self._names_list[1]) + \
                      '&group_by=*' + \
                      '&merge_metrics=true' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        resp, response_body = self.monasca_client.list_measurements(
            query_parms)
        self.assertEqual(200, resp.status)
        elements = response_body['elements']
        self.assertEqual(len(elements), 4)
        self._verify_list_measurements_elements(elements, None, None)

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_name_exceeds_max_length(self):
        long_name = "x" * (constants.MAX_LIST_MEASUREMENTS_NAME_LENGTH + 1)
        query_parms = '?name=' + str(long_name) + '&merge_metrics=true' + \
                      '&start_time=' + str(self._start_time) + \
                      '&end_time=' + str(self._end_time)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.list_measurements, query_parms)

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_no_merge_metrics(self):
        query_parms = '?name=' + str(self._names_list[0]) + \
                      '&start_time=' + str(self._start_time) + '&end_time=' \
                      + str(self._end_time)
        self.assertRaises(exceptions.Conflict,
                          self.monasca_client.list_measurements, query_parms)

    @test.attr(type="gate")
    def test_list_measurements_with_duplicate_query_param_merges_positive(
            self):
        queries = []
        queries.append('?name={}&merge_metrics=true&start_time={}&end_time={'
                       '}&merge_metrics=true'.
                       format(self._names_list[0], self._start_time,
                              self._end_time))
        queries.append('?name={}&merge_metrics=true&start_time={}&end_time={'
                       '}&merge_metrics=false'.
                       format(self._names_list[0], self._start_time,
                              self._end_time))
        responses = map(self.monasca_client.list_measurements, queries)
        for i in xrange(2):
            self._verify_list_measurements(responses[i][0], responses[i][1])

    @test.attr(type="gate")
    @test.attr(type=['negative'])
    def test_list_measurements_with_duplicate_query_param_merges_negative(
            self):
        queries = []
        queries.append('?name={}&merge_metrics=false&start_time={}&end_time={'
                       '}&merge_metrics=true'.
                       format(self._names_list[0], self._start_time,
                              self._end_time))
        queries.append('?name={}&merge_metrics=false&start_time={}&end_time={'
                       '}&merge_metrics=false'.
                       format(self._names_list[0], self._start_time,
                              self._end_time))
        for i in xrange(2):
            self.assertRaises(exceptions.Conflict,
                              self.monasca_client.list_measurements,
                              queries[i])

    def _verify_list_measurements_measurement(self, measurement, test_value):
        self.assertEqual(test_value, float(measurement[1]))

    def _verify_list_measurements(self, resp, response_body):
        self.assertEqual(200, resp.status)
        self.assertTrue(set(['links', 'elements']) == set(response_body))

    def _verify_list_measurements_elements(self, elements, test_key,
                                           test_value):
        if not elements:
            error_msg = "Failed: at least one element is needed. " \
                        "Number of element = 0."
            self.fail(error_msg)

        for element in elements:
            # element = elements[0]
            self.assertEqual(set(element),
                             set(['columns', 'dimensions', 'id',
                                  'measurements', 'name']))
            self.assertTrue(type(element['name']) is unicode)
            self.assertTrue(type(element['dimensions']) is dict)
            self.assertTrue(type(element['columns']) is list)
            self.assertTrue(type(element['measurements']) is list)
            self.assertEqual(set(element['columns']),
                             set(['timestamp', 'value', 'value_meta']))
            self.assertTrue(str(element['id']) is not None)
            if test_key is not None and test_value is not None:
                self.assertEqual(str(element['dimensions'][test_key]),
                                 test_value)

    def _verify_list_measurements_meas_len(self, measurements, test_len):
        if measurements:
            len_measurements = len(measurements)
            self.assertEqual(len_measurements, test_len)
        else:
            error_msg = "Failed: one specific measurement is needed. " \
                        "Number of measurements = 0"
            self.fail(error_msg)
