# Copyright (c) 2014 OpenStack Foundation.
# All Rights Reserved.
#
# 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 re

try:
    # integration with congress is optional
    import congressclient.v1.client as congress_client
except ImportError as congress_client_import_error:
    congress_client = None
from oslo_log import log as logging

from murano.common import auth_utils
from murano.common.i18n import _, _LI
from murano.policy import congress_rules
from murano.policy.modify.actions import action_manager as am


LOG = logging.getLogger(__name__)


class ValidationError(Exception):
    """Raised for validation errors."""
    pass


class ModelPolicyEnforcer(object):
    """Policy Enforcer Implementation using Congress client

    Converts murano model to list of congress data rules.

    We ask congress using simulation api of congress rest client
    to resolve "murano_system:predeploy_errors(env_id, obj_id, msg)"
    table along with congress data rules to return validation results.
    """

    def __init__(self, execution_session, action_manager=None):
        self._execution_session = execution_session
        self._action_manager = action_manager or am.ModifyActionManager()
        self._client = None

    def _create_client(self):
        if not congress_client:
            # congress client was not imported
            raise congress_client_import_error
        return congress_client.Client(
            **auth_utils.get_session_client_parameters(
                service_type='policy',
                execution_session=self._execution_session))

    @property
    def client(self):
        if self._client is None:
            self._client = self._create_client()
        return self._client

    def modify(self, obj, package_loader=None):
        """Modifies model using Congress rule engine.

        @type obj: object model
        @param obj: Representation of model starting on
                      environment level (['Objects'])
        @type class_loader: murano.dsl.class_loader.MuranoClassLoader
        @param class_loader: Optional. Used for evaluating parent class types
        @raises ValidationError in case validation was not successful
        """

        model = obj.to_dictionary()

        LOG.debug('Modifying model')
        LOG.debug(model)

        env_id = model['?']['id']

        result = self._execute_simulation(package_loader, env_id, model,
                                          'predeploy_modify(eid, oid, action)')

        raw_actions = result["result"]
        if raw_actions:
            actions = self._parse_simulation_result('predeploy_modify',
                                                    env_id, raw_actions)
            for action in actions:
                self._action_manager.apply_action(obj, action)

    def validate(self, model, package_loader=None):
        """Validate model using Congress rule engine.

        @type model: dict
        @param model: Dictionary representation of model starting on
                      environment level (['Objects'])
        @type package_loader: murano.dsl.package_loader.MuranoPackageLoader
        @param package_loader: Optional. Used for evaluating parent class types
        @raises ValidationError in case validation was not successful
        """

        if model is None:
            return

        env_id = model['?']['id']

        validation_result = self._execute_simulation(
            package_loader, env_id, model,
            'predeploy_errors(eid, oid, msg)')

        if validation_result["result"]:

            messages = self._parse_simulation_result(
                'predeploy_errors', env_id,
                validation_result["result"])

            if messages:
                result_str = "\n  ".join(map(str, messages))
                msg = _("Murano object model validation failed: {0}").format(
                    "\n  " + result_str)
                LOG.error(msg)
                raise ValidationError(msg)
        else:
            LOG.info(_LI('Model valid'))

    def _execute_simulation(self, package_loader, env_id, model, query):
        rules = congress_rules.CongressRulesManager().convert(
            model, package_loader, self._execution_session.project_id)
        rules_str = list(map(str, rules))
        # cleanup of data populated by murano driver
        rules_str.insert(0, 'deleteEnv("{0}")'.format(env_id))
        rules_line = " ".join(rules_str)
        LOG.debug('Congress rules: \n  {rules} '
                  .format(rules='\n  '.join(rules_str)))

        validation_result = self.client.execute_policy_action(
            "murano_system",
            "simulate",
            False,
            False,
            {'query': query,
             'action_policy': 'murano_action',
             'sequence': rules_line})
        return validation_result

    @staticmethod
    def _parse_simulation_result(query, env_id, results):
        """Transforms the list of results

        Transforms a list of strings in format:

        ['predeploy_errors("env_id_1", "obj_id_1", "message1")',
        'predeploy_errors("env_id_2", "obj_id_2", "message2")']

        to a list of strings with message only filtered to provided
        env_id (e.g. 'env_id_1'):

        ['message2']
        """

        messages = []
        regexp = query + '\("([^"]*)",\s*"([^"]*)",\s*"(.*)"\)'
        for result in results:
            match = re.search(regexp, result)
            if match:
                if env_id in match.group(1):
                    messages.append(match.group(3))

        return messages
