# Copyright 2015 Cray
# Copyright 2016-2017 FUJITSU LIMITED
# (C) Copyright 2016-2017 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 datetime

import fixtures
from oslo_config import cfg
from oslo_db.sqlalchemy.engines import create_engine
from sqlalchemy import delete, MetaData, insert, bindparam, select, func

from monasca_api.common.repositories import exceptions
from monasca_api.common.repositories.model import sub_alarm_definition
from monasca_api.common.repositories.sqla import models
from monasca_api.expression_parser import alarm_expr_parser
from monasca_api.tests import base

CONF = cfg.CONF
ALARM_DEF_123_FIELDS = {'actions_enabled': False,
                        'alarm_actions': u'29387234,77778687',
                        'description': None,
                        'expression': u'AVG(hpcs.compute{flavor_id=777, '
                        'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                        'id': u'123',
                        'match_by': u'flavor_id,image_id',
                        'name': u'90% CPU',
                        'ok_actions': None,
                        'severity': u'LOW',
                        'undetermined_actions': None}
TENANT_ID = 'bob'


class TestAlarmDefinitionRepoDB(base.BaseTestCase):

    @classmethod
    def setUpClass(cls):
        engine = create_engine('sqlite://')
        qry = open('monasca_api/tests/sqlite_alarm.sql', 'r').read()
        sconn = engine.raw_connection()
        c = sconn.cursor()
        c.executescript(qry)
        sconn.commit()
        c.close()
        cls.engine = engine

        def _fake_engine_from_config(*args, **kw):
            return cls.engine

        cls.fixture = fixtures.MonkeyPatch(
            'sqlalchemy.create_engine', _fake_engine_from_config)
        cls.fixture.setUp()
        metadata = MetaData()

        cls.aa = models.create_aa_model(metadata)
        cls._delete_aa_query = delete(cls.aa)
        cls._insert_aa_query = (insert(cls.aa).values(
            alarm_definition_id=bindparam('alarm_definition_id'),
            alarm_state=bindparam('alarm_state'),
            action_id=bindparam('action_id')))

        cls.ad = models.create_ad_model(metadata)
        cls._delete_ad_query = delete(cls.ad)
        cls._insert_ad_query = (insert(cls.ad).values(
            id=bindparam('id'),
            tenant_id=bindparam('tenant_id'),
            name=bindparam('name'),
            severity=bindparam('severity'),
            expression=bindparam('expression'),
            match_by=bindparam('match_by'),
            actions_enabled=bindparam('actions_enabled'),
            created_at=bindparam('created_at'),
            updated_at=bindparam('updated_at'),
            deleted_at=bindparam('deleted_at')))

        cls.sad = models.create_sad_model(metadata)
        cls._delete_sad_query = delete(cls.sad)
        cls._insert_sad_query = (insert(cls.sad).values(
            id=bindparam('id'),
            alarm_definition_id=bindparam('alarm_definition_id'),
            function=bindparam('function'),
            metric_name=bindparam('metric_name'),
            operator=bindparam('operator'),
            threshold=bindparam('threshold'),
            period=bindparam('period'),
            periods=bindparam('periods'),
            is_deterministic=bindparam('is_deterministic'),
            created_at=bindparam('created_at'),
            updated_at=bindparam('updated_at')))

        cls.sadd = models.create_sadd_model(metadata)
        cls._delete_sadd_query = delete(cls.sadd)
        cls._insert_sadd_query = (insert(cls.sadd).values(
            sub_alarm_definition_id=bindparam('sub_alarm_definition_id'),
            dimension_name=bindparam('dimension_name'),
            value=bindparam('value')))

        cls.nm = models.create_nm_model(metadata)
        cls._delete_nm_query = delete(cls.nm)
        cls._insert_nm_query = (insert(cls.nm).values(
            id=bindparam('id'),
            tenant_id=bindparam('tenant_id'),
            name=bindparam('name'),
            type=bindparam('type'),
            address=bindparam('address'),
            created_at=bindparam('created_at'),
            updated_at=bindparam('updated_at')))

    @classmethod
    def tearDownClass(cls):
        cls.fixture.cleanUp()
        if hasattr(CONF, 'sql_engine'):
            delattr(CONF, 'sql_engine')

    def setUp(self):
        super(TestAlarmDefinitionRepoDB, self).setUp()
        self.conf_default(connection='sqlite://', group='database')

        from monasca_api.common.repositories.sqla import alarm_definitions_repository as adr
        self.repo = adr.AlarmDefinitionsRepository()
        self.default_ads = [{'id': '123',
                             'tenant_id': 'bob',
                             'name': '90% CPU',
                             'severity': 'LOW',
                             'expression': 'AVG(hpcs.compute{flavor_id=777,'
                             ' image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                             'match_by': 'flavor_id,image_id',
                             'actions_enabled': False,
                             'created_at': datetime.datetime.now(),
                             'updated_at': datetime.datetime.now(),
                             'deleted_at': None},
                            {'id': '234',
                             'tenant_id': 'bob',
                             'name': '50% CPU',
                             'severity': 'LOW',
                             'expression': 'AVG(hpcs.compute{flavor_id=777, '
                             'image_id=888, metric_name=mem}) > 20'
                             ' and AVG(hpcs.compute) < 100',
                             'match_by': 'flavor_id,image_id',
                             'actions_enabled': False,
                             'created_at': datetime.datetime.now(),
                             'updated_at': datetime.datetime.now(),
                             'deleted_at': None}]

        self.default_sads = [{'id': '111',
                              'alarm_definition_id': '123',
                              'function': 'AVG',
                              'metric_name': 'hpcs.compute',
                              'operator': 'GT',
                              'threshold': 10,
                              'period': 60,
                              'periods': 1,
                              'is_deterministic': True,
                              'created_at': datetime.datetime.now(),
                              'updated_at': datetime.datetime.now()},
                             {'id': '222',
                              'alarm_definition_id': '234',
                              'function': 'AVG',
                              'metric_name': 'hpcs.compute',
                              'operator': 'GT',
                              'threshold': 20,
                              'period': 60,
                              'periods': 1,
                              'is_deterministic': False,
                              'created_at': datetime.datetime.now(),
                              'updated_at': datetime.datetime.now()},
                             {'id': '223',
                              'alarm_definition_id': '234',
                              'function': 'AVG',
                              'metric_name': 'hpcs.compute',
                              'operator': 'LT',
                              'threshold': 100,
                              'period': 60,
                              'periods': 1,
                              'is_deterministic': False,
                              'created_at': datetime.datetime.now(),
                              'updated_at': datetime.datetime.now()},
                             ]

        self.default_sadds = [{'sub_alarm_definition_id': '111',
                               'dimension_name': 'flavor_id',
                               'value': '777'},
                              {'sub_alarm_definition_id': '111',
                               'dimension_name': 'image_id',
                               'value': '888'},
                              {'sub_alarm_definition_id': '111',
                               'dimension_name': 'metric_name',
                               'value': 'cpu'},
                              {'sub_alarm_definition_id': '111',
                               'dimension_name': 'device',
                               'value': '1'},
                              {'sub_alarm_definition_id': '222',
                               'dimension_name': 'flavor_id',
                               'value': '777'},
                              {'sub_alarm_definition_id': '222',
                               'dimension_name': 'image_id',
                               'value': '888'},
                              {'sub_alarm_definition_id': '222',
                               'dimension_name': 'metric_name',
                               'value': 'mem'}]

        self.default_nms = [{'id': '29387234',
                             'tenant_id': 'alarm-test',
                             'name': 'MyEmail',
                             'type': 'EMAIL',
                             'address': 'a@b',
                             'created_at': datetime.datetime.now(),
                             'updated_at': datetime.datetime.now()},
                            {'id': '77778687',
                             'tenant_id': 'alarm-test',
                             'name': 'OtherEmail',
                             'type': 'EMAIL',
                             'address': 'a@b',
                             'created_at': datetime.datetime.now(),
                             'updated_at': datetime.datetime.now()}]

        self.default_aas = [{'alarm_definition_id': '123',
                             'alarm_state': 'ALARM',
                             'action_id': '29387234'},
                            {'alarm_definition_id': '123',
                             'alarm_state': 'ALARM',
                             'action_id': '77778687'},
                            {'alarm_definition_id': '234',
                             'alarm_state': 'ALARM',
                             'action_id': '29387234'},
                            {'alarm_definition_id': '234',
                             'alarm_state': 'ALARM',
                             'action_id': '77778687'}]

        with self.engine.begin() as conn:
            conn.execute(self._delete_ad_query)
            conn.execute(self._insert_ad_query, self.default_ads)
            conn.execute(self._delete_sad_query)
            conn.execute(self._insert_sad_query, self.default_sads)
            conn.execute(self._delete_sadd_query)
            conn.execute(self._insert_sadd_query, self.default_sadds)
            conn.execute(self._delete_nm_query)
            conn.execute(self._insert_nm_query, self.default_nms)
            conn.execute(self._delete_aa_query)
            conn.execute(self._insert_aa_query, self.default_aas)

    def test_should_create(self):
        expression = ('AVG(hpcs.compute{flavor_id=777, image_id=888,'
                      ' metric_name=cpu}) > 10')
        description = ''
        match_by = ['flavor_id', 'image_id']
        sub_expr_list = (alarm_expr_parser.AlarmExprParser(expression).sub_expr_list)
        alarm_actions = ['29387234', '77778687']
        alarmA_id = self.repo.create_alarm_definition('555',
                                                      '90% CPU',
                                                      expression,
                                                      sub_expr_list,
                                                      description,
                                                      'LOW',
                                                      match_by,
                                                      alarm_actions,
                                                      None,
                                                      None)

        alarmB = self.repo.get_alarm_definition('555', alarmA_id)

        self.assertEqual(alarmA_id, alarmB['id'])

        query_sad = (select([self.sad.c.id])
                     .select_from(self.sad)
                     .where(self.sad.c.alarm_definition_id == alarmA_id))

        query_sadd = (select([func.count()])
                      .select_from(self.sadd)
                      .where(self.sadd.c.sub_alarm_definition_id == bindparam('id')))

        with self.engine.connect() as conn:
            count_sad = conn.execute(query_sad).fetchall()
            self.assertEqual(len(count_sad), 1)
            count_sadd = conn.execute(query_sadd, id=count_sad[0][0]).fetchone()
            self.assertEqual(count_sadd[0], 3)

    def test_should_try_to_create_with_wrong_alarm_action(self):
        expression = ('AVG(hpcs.compute{flavor_id=777, image_id=888,'
                      ' metric_name=cpu}) > 10')
        description = ''
        match_by = ['flavor_id', 'image_id']
        sub_expr_list = alarm_expr_parser.AlarmExprParser(expression).sub_expr_list
        alarm_actions = ['66666666']
        args = ('555',
                '90% CPU',
                expression,
                sub_expr_list,
                description,
                'LOW',
                match_by,
                alarm_actions,
                None,
                None)
        self.assertRaises(exceptions.InvalidUpdateException,
                          self.repo.create_alarm_definition, *args)

    def test_should_update(self):
        expression = ''.join(['AVG(hpcs.compute{flavor_id=777, image_id=888,',
                              ' metric_name=mem}) > 20 and',
                              ' AVG(hpcs.compute) < 100'])
        description = ''
        match_by = ['flavor_id', 'image_id']
        sub_expr_list = (alarm_expr_parser.AlarmExprParser(expression).sub_expr_list)
        alarm_actions = ['29387234', '77778687']
        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   '90% CPU', expression,
                                                   sub_expr_list, False,
                                                   description, alarm_actions,
                                                   None, None,
                                                   match_by, 'LOW')
        alarm = self.repo.get_alarm_definition('bob', '234')
        expected = {'actions_enabled': False,
                    'alarm_actions': '29387234,77778687',
                    'description': '',
                    'expression': 'AVG(hpcs.compute{flavor_id=777, '
                    'image_id=888, metric_name=mem}) > 20 and'
                    ' AVG(hpcs.compute) < 100',
                    'id': '234',
                    'match_by': 'flavor_id,image_id',
                    'name': '90% CPU',
                    'ok_actions': None,
                    'severity': 'LOW',
                    'undetermined_actions': None}

        self.assertEqual(alarm, expected)

        sub_alarms = self.repo.get_sub_alarm_definitions('234')

        expected = [{'alarm_definition_id': '234',
                     'dimensions': 'flavor_id=777,image_id=888,'
                     'metric_name=mem',
                     'function': 'AVG',
                     'id': '222',
                     'metric_name': 'hpcs.compute',
                     'operator': 'GT',
                     'period': 60,
                     'periods': 1,
                     'is_deterministic': False,
                     'threshold': 20.0},
                    {'alarm_definition_id': '234',
                     'dimensions': None,
                     'function': 'AVG',
                     'id': '223',
                     'metric_name': 'hpcs.compute',
                     'operator': 'LT',
                     'period': 60,
                     'periods': 1,
                     'is_deterministic': False,
                     'threshold': 100.0}]

        self.assertEqual(len(sub_alarms), len(expected))

        for s, e in zip(sub_alarms, expected):
            e['created_at'] = s['created_at']
            e['updated_at'] = s['updated_at']

        self.assertEqual(sub_alarms, expected)
        am = self.repo.get_alarm_metrics('bob', '234')
        self.assertEqual(am, [])

        sub_alarms = self.repo.get_sub_alarms('bob', '234')
        self.assertEqual(sub_alarms, [])

        ads = self.repo.get_alarm_definitions(
            'bob', '90% CPU', {
                'image_id': '888'}, None, None, 0, 100)
        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None},
                    {'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': '',
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 and'
                     ' AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]
        self.assertEqual(ads, expected)

        ads = self.repo.get_alarm_definitions(
            'bob', '90% CPU', {
                'image_id': '888'}, 'LOW', None, 0, 100)
        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None},
                    {'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': '',
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 and'
                     ' AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]
        self.assertEqual(ads, expected)

        ads = self.repo.get_alarm_definitions(
            'bob', '90% CPU', {
                'image_id': '888'}, 'CRITICAL', None, 0, 100)
        expected = []
        self.assertEqual(ads, expected)

        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   '90% CPU', None,
                                                   sub_expr_list, False,
                                                   description, alarm_actions,
                                                   None, None,
                                                   match_by, 'LOW')

        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   None, None,
                                                   None, True,
                                                   None, None,
                                                   None, None,
                                                   None, None,
                                                   True)

        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   None, None,
                                                   None, None,
                                                   None, None,
                                                   None, None,
                                                   None, None,
                                                   True)

        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   None, None,
                                                   None, None,
                                                   None, [],
                                                   [], [],
                                                   None, None,
                                                   True)

        self.repo.update_or_patch_alarm_definition('bob', '234',
                                                   None, None,
                                                   None, False,
                                                   None, None,
                                                   None, None,
                                                   match_by, None,
                                                   False)

        self.assertRaises(exceptions.InvalidUpdateException,
                          self.repo.update_or_patch_alarm_definition,
                          'bob', '234',
                          None, None,
                          None, False,
                          None, None,
                          None, None,
                          None, None,
                          False)

        self.assertRaises(exceptions.InvalidUpdateException,
                          self.repo.update_or_patch_alarm_definition,
                          'bob', '234',
                          '90% CPU', None,
                          sub_expr_list, False,
                          description, alarm_actions,
                          None, None,
                          'update_match_by', 'LOW')

        self.repo.delete_alarm_definition('bob', '234')

        self.assertRaises(exceptions.DoesNotExistException,
                          self.repo.get_alarm_definition, 'bob', '234')

    def test_should_find_by_id(self):
        alarmDef1 = self.repo.get_alarm_definition('bob', '123')
        expected = {'actions_enabled': False,
                    'alarm_actions': '29387234,77778687',
                    'description': None,
                    'expression': 'AVG(hpcs.compute{flavor_id=777, '
                    'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                    'id': '123',
                    'match_by': 'flavor_id,image_id',
                    'name': '90% CPU',
                    'ok_actions': None,
                    'severity': 'LOW',
                    'undetermined_actions': None}
        self.assertEqual(alarmDef1, expected)
        with self.engine.begin() as conn:
            conn.execute(self._delete_aa_query)

        alarmDef2 = self.repo.get_alarm_definition('bob', '123')
        expected['alarm_actions'] = None
        self.assertEqual(alarmDef2, expected)

    def test_shoud_find_sub_alarm_metric_definitions(self):
        sub_alarms = self.repo.get_sub_alarm_definitions('123')

        expected = [{'alarm_definition_id': '123',
                     'dimensions': 'flavor_id=777,image_id=888,'
                     'metric_name=cpu,device=1',
                     'function': 'AVG',
                     'id': '111',
                     'metric_name': 'hpcs.compute',
                     'operator': 'GT',
                     'period': 60,
                     'periods': 1,
                     'is_deterministic': True,
                     'threshold': 10.0}]

        self.assertEqual(len(sub_alarms), len(expected))

        for s, e in zip(sub_alarms, expected):
            e['created_at'] = s['created_at']
            e['updated_at'] = s['updated_at']

        self.assertEqual(sub_alarms, expected)

        sub_alarms = self.repo.get_sub_alarm_definitions('234')

        expected = [{'alarm_definition_id': '234',
                     'dimensions': 'flavor_id=777,image_id=888,metric_name=mem',
                     'function': 'AVG',
                     'id': '222',
                     'metric_name': 'hpcs.compute',
                     'operator': 'GT',
                     'period': 60,
                     'periods': 1,
                     'is_deterministic': False,
                     'threshold': 20.0},
                    {'alarm_definition_id': '234',
                     'dimensions': None,
                     'function': 'AVG',
                     'id': '223',
                     'metric_name': 'hpcs.compute',
                     'operator': 'LT',
                     'period': 60,
                     'periods': 1,
                     'is_deterministic': False,
                     'threshold': 100.0}]

        self.assertEqual(len(sub_alarms), len(expected))

        for s, e in zip(sub_alarms, expected):
            e['created_at'] = s['created_at']
            e['updated_at'] = s['updated_at']

        self.assertEqual(sub_alarms, expected)

        sub_alarms = self.repo.get_sub_alarm_definitions('asdfasdf')
        self.assertEqual(sub_alarms, [])

    def test_try_update_alarm_that_does_not_exist(self):
        args = ('koala', '999',
                None, None,
                None, True,
                None, None,
                None, None,
                None, None,
                True)
        self.assertRaises(exceptions.DoesNotExistException,
                          self.repo.update_or_patch_alarm_definition, *args)

    def test_exists(self):
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    name='90% CPU')
        expected = {'actions_enabled': False,
                    'alarm_actions': '29387234,77778687',
                    'description': None,
                    'expression': 'AVG(hpcs.compute{flavor_id=777, '
                    'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                    'id': '123',
                    'match_by': 'flavor_id,image_id',
                    'name': '90% CPU',
                    'ok_actions': None,
                    'severity': 'LOW',
                    'undetermined_actions': None}

        self.assertEqual(alarmDef1, [expected])
        alarmDef2 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    name='999% CPU')
        self.assertEqual(alarmDef2, [])

    def test_should_find(self):
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    limit=1)
        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None},
                    {'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 and '
                     'AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '50% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        self.assertEqual(alarmDef1, expected)
        with self.engine.begin() as conn:
            conn.execute(self._delete_aa_query)

        alarmDef2 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    limit=1)
        expected[0]['alarm_actions'] = None
        expected[1]['alarm_actions'] = None
        self.assertEqual(alarmDef2, expected)

        alarmDef3 = self.repo.get_alarm_definitions(tenant_id='bill',
                                                    limit=1)
        self.assertEqual(alarmDef3, [])

        alarmDef3 = self.repo.get_alarm_definitions(tenant_id='bill',
                                                    offset='10',
                                                    limit=1)
        self.assertEqual(alarmDef3, [])

    def test_should_find_by_dimension(self):
        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777,'
                     ' image_id=888, metric_name=mem}) > 20 '
                     'and AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '50% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        dimensions = {'image_id': '888', 'metric_name': 'mem'}
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    dimensions=dimensions,
                                                    limit=1)
        self.assertEqual(alarmDef1, expected)

        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None},
                    {'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 and '
                     'AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '50% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        dimensions = {'image_id': '888'}
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    dimensions=dimensions,
                                                    limit=1)
        self.assertEqual(alarmDef1, expected)

        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        dimensions = {'device': '1'}
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    dimensions=dimensions,
                                                    limit=1)
        self.assertEqual(alarmDef1, expected)

        dimensions = {'Not real': 'AA'}
        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    dimensions=dimensions,
                                                    limit=1)
        self.assertEqual(alarmDef1, [])

    def test_should_find_and_sort(self):
        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=cpu, device=1},deterministic) > 10',
                     'id': '123',
                     'match_by': 'flavor_id,image_id',
                     'name': '90% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None},
                    {'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 and '
                     'AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '50% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    sort_by=['id'])
        self.assertEqual(expected, alarmDef1)
        alarmDef2 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    sort_by=['name'])
        self.assertEqual(expected[::-1], alarmDef2)

    def test_should_delete_by_id(self):
        self.repo.delete_alarm_definition('bob', '123')
        from monasca_api.common.repositories import exceptions
        self.assertRaises(exceptions.DoesNotExistException,
                          self.repo.get_alarm_definition,
                          'bob',
                          '123')

        expected = [{'actions_enabled': False,
                     'alarm_actions': '29387234,77778687',
                     'description': None,
                     'expression': 'AVG(hpcs.compute{flavor_id=777, '
                     'image_id=888, metric_name=mem}) > 20 '
                     'and AVG(hpcs.compute) < 100',
                     'id': '234',
                     'match_by': 'flavor_id,image_id',
                     'name': '50% CPU',
                     'ok_actions': None,
                     'severity': 'LOW',
                     'undetermined_actions': None}]

        alarmDef1 = self.repo.get_alarm_definitions(tenant_id='bob',
                                                    limit=1)
        self.assertEqual(alarmDef1, expected)

    def test_try_delete_alarm_that_does_not_exist(self):
        response = self.repo.delete_alarm_definition('goku', '123')
        self.assertEqual(False, response)

    def test_should_patch_name(self):
        self.run_patch_test(name=u'90% CPU New')

    def test_should_patch_description(self):
        self.run_patch_test(description=u'New Description')

    def test_should_patch_severity(self):
        self.run_patch_test(severity=u'CRITICAL')

    def test_should_patch_actions_enabled(self):
        self.run_patch_test(actions_enabled=False)

    def test_should_patch_ok_actions(self):
        self.run_patch_test(ok_actions=[u'29387234'])

    def test_should_patch_alarm_actions(self):
        self.run_patch_test(alarm_actions=[u'29387234'])

    def test_should_patch_undetermined_actions(self):
        self.run_patch_test(undetermined_actions=[u'29387234', u'77778687'])

    def test_should_patch_match_by(self):
        # match_by can't change, so make sure old value works
        self.run_patch_test(match_by=[u'flavor_id', u'image_id'])

    def test_should_patch_expression_no_change(self):
        # match_by can't change, so make sure old value works
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'])

    def test_should_patch_expression_threshold_change(self):
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'].replace(' 10', ' 20'))

    def test_should_patch_expression_deterministic_change(self):
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'].replace(',deterministic',
                                                                                  ''))

    def test_should_patch_expression_function_change(self):
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'].replace('AVG', 'MAX'))

    def test_should_patch_expression_operation_change(self):
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'].replace('>', '<'))

    def test_should_patch_expression_period_change(self):
        self.run_patch_test(expression=ALARM_DEF_123_FIELDS['expression'].replace(')', ', 120)'))

    def test_should_patch_expression_periods_change(self):
        self.run_patch_test(
            expression=ALARM_DEF_123_FIELDS['expression'].replace(
                ' 10', ' 10 times 2'))

    def test_patch_fails_change_match_by(self):
        self.assertRaises(
            exceptions.InvalidUpdateException,
            self.run_patch_test,
            match_by=u'device')

    def test_patch_fails_change_metric_name(self):
        self.assertRaises(exceptions.InvalidUpdateException, self.run_patch_test,
                          expression=ALARM_DEF_123_FIELDS['expression'].replace('hpcs.compute',
                                                                                'new_metric_name'))

    def test_patch_fails_change_metric_dimensions(self):
        self.assertRaises(exceptions.InvalidUpdateException, self.run_patch_test,
                          expression=ALARM_DEF_123_FIELDS['expression'].replace('image_id=888',
                                                                                'image_id=42'))

    def test_patch_fails_change_num_sub_expressions(self):
        self.assertRaises(exceptions.InvalidUpdateException, self.run_patch_test,
                          expression=ALARM_DEF_123_FIELDS['expression']
                          .replace(' 10', ' 10 and MAX(cpu.idle_perc) < 10'))

    def run_patch_test(self, name=None, expression=None, description=None, actions_enabled=None,
                       alarm_actions=None, ok_actions=None, undetermined_actions=None,
                       match_by=None, severity=None):
        if expression:
            sub_expr_list = (alarm_expr_parser.AlarmExprParser(expression).sub_expr_list)
        else:
            sub_expr_list = None
        updates = self.repo.update_or_patch_alarm_definition(TENANT_ID, '123',
                                                             name, expression,
                                                             sub_expr_list, actions_enabled,
                                                             description, alarm_actions,
                                                             ok_actions, undetermined_actions,
                                                             match_by, severity,
                                                             patch=True)

        alarm_def_row = \
            (ALARM_DEF_123_FIELDS['id'],
             name if name else ALARM_DEF_123_FIELDS['name'],
             description if description else ALARM_DEF_123_FIELDS['description'],
             expression if expression else ALARM_DEF_123_FIELDS['expression'],
             ALARM_DEF_123_FIELDS['match_by'],  # match-by can't change
             severity if severity else ALARM_DEF_123_FIELDS['severity'],
             actions_enabled if actions_enabled else ALARM_DEF_123_FIELDS['actions_enabled'],
             u','.join(alarm_actions) if alarm_actions else ALARM_DEF_123_FIELDS['alarm_actions'],
             u','.join(ok_actions) if ok_actions else ALARM_DEF_123_FIELDS['ok_actions'],
             (u','.join(undetermined_actions) if undetermined_actions else
              ALARM_DEF_123_FIELDS['undetermined_actions']))

        sad = self.default_sads[0]
        if expression and ALARM_DEF_123_FIELDS['expression'] != expression:
            sub_expr = sub_expr_list[0]
            sub_alarm_def = sub_alarm_definition.SubAlarmDefinition(
                row={'id': '',
                     'alarm_definition_id': sad['alarm_definition_id'],
                     'function': sub_expr.normalized_func,
                     'metric_name': sub_expr.metric_name,
                     'dimensions': u'device=1,image_id=888,flavor_id=777,metric_name=cpu',
                     'operator': sub_expr.normalized_operator,
                     'threshold': sub_expr.threshold,
                     'period': sub_expr.period,
                     'is_deterministic': sub_expr.deterministic,
                     'periods': sub_expr.periods})
            expected_sub_alarm_maps = {
                'changed': {
                    u'111': sub_alarm_def},
                'new': {},
                'old': {},
                'unchanged': {}}
        else:
            sub_alarm_def = sub_alarm_definition.SubAlarmDefinition(
                row={'id': sad['id'],
                     'alarm_definition_id': sad['alarm_definition_id'],
                     'function': sad['function'],
                     'metric_name': sad['metric_name'],
                     'dimensions': u'device=1,image_id=888,flavor_id=777,metric_name=cpu',
                     'operator': sad['operator'],
                     'threshold': sad['threshold'],
                     'period': sad['period'],
                     'is_deterministic': sad['is_deterministic'],
                     'periods': sad['periods']})
            expected_sub_alarm_maps = {'changed': {}, 'new': {},
                                       'old': {}, 'unchanged': {u'111': sub_alarm_def}}
        self.assertEqual((alarm_def_row, expected_sub_alarm_maps), updates)
