#    Copyright (c) 2013 Mirantis, Inc.
#
#    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 uuid

from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_messaging import target
from oslo_service import service
from oslo_utils import timeutils
import pytz
from sqlalchemy import desc

from murano.common.helpers import token_sanitizer
from murano.db import models
from murano.db.services import environments
from murano.db.services import instances
from murano.db import session
from murano.common.i18n import _LI, _LW
from murano.services import states

CONF = cfg.CONF

LOG = logging.getLogger(__name__)


class ResultEndpoint(object):
    @staticmethod
    def process_result(context, result, environment_id):
        secure_result = token_sanitizer.TokenSanitizer().sanitize(result)
        LOG.debug('Got result from orchestration '
                  'engine:\n{result}'.format(result=secure_result))

        model = result['model']
        action_result = result.get('action', {})

        unit = session.get_session()
        environment = unit.query(models.Environment).get(environment_id)

        if not environment:
            LOG.warning(_LW('Environment result could not be handled, '
                            'specified environment not found in database'))
            return

        if model['Objects'] is None and model.get('ObjectsCopy', {}) is None:
            environments.EnvironmentServices.remove(environment_id)
            return

        environment.description = model
        if environment.description['Objects'] is not None:
            environment.description['Objects']['services'] = \
                environment.description['Objects'].pop('applications', [])
            action_name = 'Deployment'
            deleted = False
        else:
            action_name = 'Deletion'
            deleted = True
        environment.version += 1
        environment.save(unit)

        # close deployment
        deployment = get_last_deployment(unit, environment.id)
        deployment.finished = timeutils.utcnow()
        deployment.result = action_result

        num_errors = unit.query(models.Status)\
            .filter_by(level='error', task_id=deployment.id).count()
        num_warnings = unit.query(models.Status)\
            .filter_by(level='warning', task_id=deployment.id).count()

        final_status_text = action_name + ' finished'
        if num_errors:
            final_status_text += " with errors"

        elif num_warnings:
            final_status_text += " with warnings"

        status = models.Status()
        status.task_id = deployment.id
        status.text = final_status_text
        status.level = 'info'
        deployment.statuses.append(status)
        deployment.save(unit)

        # close session
        conf_session = unit.query(models.Session).filter_by(
            **{'environment_id': environment.id,
               'state': states.SessionState.DEPLOYING if not deleted
               else states.SessionState.DELETING}).first()
        if num_errors > 0 or result['action'].get('isException'):
            conf_session.state = \
                states.SessionState.DELETE_FAILURE if deleted else \
                states.SessionState.DEPLOY_FAILURE
        else:
            conf_session.state = states.SessionState.DEPLOYED
        conf_session.save(unit)

        # output application tracking information
        services = []
        objects = model['Objects']
        if objects:
            services = objects.get('services')
        if num_errors + num_warnings > 0:
            LOG.warning(_LW('EnvId: {env_id} TenantId: {tenant_id} Status: '
                            'Failed Apps: {services}')
                        .format(env_id=environment.id,
                                tenant_id=environment.tenant_id,
                                services=services))
        else:
            LOG.info(_LI('EnvId: {env_id} TenantId: {tenant_id} Status: '
                         'Successful Apps: {services}')
                     .format(env_id=environment.id,
                             tenant_id=environment.tenant_id,
                             services=services))


def notification_endpoint_wrapper(priority='info'):
    def wrapper(func):
        class NotificationEndpoint(object):
            def __init__(self):
                setattr(self, priority, self._handler)

            def _handler(self, ctxt, publisher_id, event_type,
                         payload, metadata):
                if event_type == ('murano.%s' % func.__name__):
                    func(payload)

            def __call__(self, payload):
                return func(payload)
        return NotificationEndpoint()
    return wrapper


@notification_endpoint_wrapper()
def track_instance(payload):
    LOG.debug('Got track instance request from orchestration '
              'engine:\n{payload}'.format(payload=payload))
    instance_id = payload['instance']
    instance_type = payload.get('instance_type', 0)
    environment_id = payload['environment']
    unit_count = payload.get('unit_count')
    type_name = payload['type_name']
    type_title = payload.get('type_title')

    instances.InstanceStatsServices.track_instance(
        instance_id, environment_id, instance_type,
        type_name, type_title, unit_count)


@notification_endpoint_wrapper()
def untrack_instance(payload):
    LOG.debug('Got untrack instance request from orchestration '
              'engine:\n{payload}'.format(payload=payload))
    instance_id = payload['instance']
    environment_id = payload['environment']
    instances.InstanceStatsServices.destroy_instance(
        instance_id, environment_id)


@notification_endpoint_wrapper()
def report_notification(report):
    LOG.debug('Got report from orchestration '
              'engine:\n{report}'.format(report=report))

    report['entity_id'] = report.pop('id')

    status = models.Status()
    if 'timestamp' in report:
        dt = timeutils.parse_isotime(report.pop('timestamp'))
        report['created'] = dt.astimezone(pytz.utc).replace(tzinfo=None)
    status.update(report)

    unit = session.get_session()
    # connect with deployment
    with unit.begin():
        running_deployment = get_last_deployment(unit,
                                                 status.environment_id)
        status.task_id = running_deployment.id
        unit.add(status)


def get_last_deployment(unit, env_id):
    query = unit.query(models.Task) \
        .filter_by(environment_id=env_id) \
        .order_by(desc(models.Task.started))
    return query.first()


class Service(service.Service):
    """Service class, that contains common methods for custom services"""

    def __init__(self):
        super(Service, self).__init__()
        self.server = None

    def stop(self, graceful=False):
        if self.server:
            self.server.stop()
            if graceful:
                self.server.wait()
        super(Service, self).stop()

    def reset(self):
        if self.server:
            self.server.reset()
        super(Service, self).reset()


class NotificationService(Service):
    def __init__(self):
        super(NotificationService, self).__init__()
        self.server = None

    def start(self):
        endpoints = [report_notification, track_instance, untrack_instance]

        transport = messaging.get_transport(CONF)
        s_target = target.Target(topic='murano', server=str(uuid.uuid4()))

        self.server = messaging.get_notification_listener(
            transport, [s_target], endpoints, executor='eventlet')

        self.server.start()
        super(NotificationService, self).start()


class ApiService(Service):

    def start(self):
        endpoints = [ResultEndpoint()]

        transport = messaging.get_transport(CONF)
        s_target = target.Target('murano', 'results', server=str(uuid.uuid4()))
        self.server = messaging.get_rpc_server(
            transport, s_target, endpoints, 'eventlet')
        self.server.start()
        super(ApiService, self).start()
