# Copyright (c) 2013-2014 Rackspace, 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.

"""
Server-side (i.e. worker side) classes and logic.
"""
import datetime
import functools

try:
    import newrelic.agent
    from newrelic.api import application
    newrelic_loaded = True
except ImportError:
    newrelic_loaded = False

from oslo_service import service

from barbican.common import config
from barbican.common import utils
from barbican import i18n as u
from barbican.model import models
from barbican.model import repositories
from barbican import queue
from barbican.tasks import common
from barbican.tasks import resources

if newrelic_loaded:
    newrelic.agent.initialize('/etc/newrelic/newrelic.ini')

LOG = utils.getLogger(__name__)

CONF = config.CONF


# Maps the common/shared RetryTasks (returned from lower-level business logic
# and plugin processing) to top-level RPC tasks in the Tasks class below.
MAP_RETRY_TASKS = {
    common.RetryTasks.INVOKE_CERT_STATUS_CHECK_TASK: 'check_certificate_status'
}


def retryable_order(fn):
    """Provides retry/scheduling support to Order-related tasks."""

    @functools.wraps(fn)
    def wrapper(method_self, *args, **kwargs):
        result = fn(method_self, *args, **kwargs)
        retry_rpc_method = schedule_order_retry_tasks(
            fn, result, *args, **kwargs)
        if retry_rpc_method:
            LOG.info(
                u._LI("Scheduled RPC method for retry: '%s'"),
                retry_rpc_method)

    return wrapper


def transactional(fn):
    """Provides request-scoped database transaction support to tasks."""

    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        fn_name = getattr(fn, '__name__', '????')

        if not queue.is_server_side():
            # Non-server mode directly invokes tasks.
            fn(*args, **kwargs)
            LOG.info(u._LI("Completed worker task: '%s'"), fn_name)
        else:
            # Manage session/transaction.
            try:
                fn(*args, **kwargs)
                repositories.commit()
                LOG.info(u._LI("Completed worker task: '%s'"), fn_name)
            except Exception:
                """NOTE: Wrapped functions must process with care!

                Exceptions that reach here will revert the entire transaction,
                including any updates made to entities such as setting error
                codes and error messages.
                """
                LOG.exception(
                    u._LE("Problem seen processing worker task: '%s'"),
                    fn_name
                )
                repositories.rollback()
            finally:
                repositories.clear()

    return wrapper


def monitored(fn):  # pragma: no cover
    """Provides monitoring capabilities for task methods."""
    # TODO(jvrbanac): Figure out how we should test third-party monitoring

    # Support NewRelic Monitoring
    if newrelic_loaded:
        # Create a NewRelic app instance
        app = application.application_instance()

        def newrelic_wrapper(*args, **kwargs):
            # Resolve real name since decorators are wrapper the method
            if len(args) > 0 and hasattr(args[0], fn.__name__):
                cls = type(args[0])
                task_name = '{0}:{1}.{2}'.format(
                    cls.__module__,
                    cls.__name__,
                    fn.__name__
                )
            else:
                task_name = newrelic.agent.callable_name(fn)

            # Execute task under a monitored context
            with newrelic.agent.BackgroundTask(app, task_name):
                fn(*args, **kwargs)

        return newrelic_wrapper

    return fn


def schedule_order_retry_tasks(
        invoked_task, retry_result, context, *args, **kwargs):
    """Schedules an Order-related task for retry.

    :param invoked_task: The RPC method that was just invoked.
    :param retry_result: A :class:`FollowOnProcessingStatusDTO` if follow-on
                         processing (such as retrying this or another task) is
                         required, otherwise None indicates no such follow-on
                         processing is required.
    :param context: Queue context, not used.
    :param order_id: ID of the Order entity the task to retry is for.
    :param args: List of arguments passed in to the just-invoked task.
    :param kwargs: Dict of arguments passed in to the just-invoked task.
    :return: Returns the RPC task method scheduled for a retry, None if no RPC
             task was scheduled.
    """

    retry_rpc_method = None
    order_id = kwargs.get('order_id')