# Copyright 2014-2015 Hewlett-Packard Development Company, L.P.
#
# 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 concurrent.futures
import datetime
import functools

from oslo_config import cfg
from oslo_log import log
from oslo_utils import uuidutils
from taskflow.conductors.backends import impl_blocking
from taskflow import engines
from taskflow import exceptions as taskflow_exc
from taskflow.listeners import base
from taskflow.listeners import logging
from taskflow.persistence import models
from taskflow import states

from octavia.amphorae.driver_exceptions import exceptions

LOG = log.getLogger(__name__)

CONF = cfg.CONF


# We do not need to log retry exception information. Warning "Could not connect
#  to instance" will be logged as usual.
def retryMaskFilter(record):
    if record.exc_info is not None and isinstance(
            record.exc_info[1], exceptions.AmpConnectionRetry):
        return False
    return True


LOG.logger.addFilter(retryMaskFilter)


class BaseTaskFlowEngine(object):
    """This is the task flow engine

    Use this engine to start/load flows in the
    code
    """

    def __init__(self):
        # work around for https://bugs.python.org/issue7980
        datetime.datetime.strptime('2014-06-19 22:47:16', '%Y-%m-%d %H:%M:%S')
        self.executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=CONF.task_flow.max_workers)

    def taskflow_load(self, flow, **kwargs):
        eng = engines.load(
            flow,
            engine=CONF.task_flow.engine,
            executor=self.executor,
            never_resolve=CONF.task_flow.disable_revert,
            **kwargs)
        eng.compile()
        eng.prepare()

        return eng


class ExtendExpiryListener(base.Listener):

    def __init__(self, engine, job):
        super().__init__(engine)
        self.job = job

    def _task_receiver(self, state, details):
        self.job.extend_expiry(cfg.CONF.task_flow.jobboard_expiration_time)

    def _flow_receiver(self, state, details):
        self.job.extend_expiry(cfg.CONF.task_flow.jobboard_expiration_time)

    def _retry_receiver(self, state, details):
        self.job.extend_expiry(cfg.CONF.task_flow.jobboard_expiration_time)


class DynamicLoggingConductor(impl_blocking.BlockingConductor):

    def _listeners_from_job(self, job, engine):
        listeners = super()._listeners_from_job(
            job, engine)
        listeners.append(logging.DynamicLoggingListener(engine, log=LOG))

        return listeners

    def _on_job_done(self, job, fut):
        super()._on_job_done(job, fut)
        # Double check that job is complete.
        if (not CONF.task_flow.jobboard_save_logbook and
                job.state == states.COMPLETE):
            LOG.debug("Job %s is complete. Cleaning up job logbook.", job.name)
            try:
                self._persistence.get_connection().destroy_logbook(
                    job.book.uuid)
            except taskflow_exc.NotFound:
                LOG.debug("Logbook for job %s has been already cleaned up",
                          job.name)


class RedisDynamicLoggingConductor(DynamicLoggingConductor):

    def _listeners_from_job(self, job, engine):
        listeners = super()._listeners_from_job(job, engine)
        listeners.append(ExtendExpiryListener(engine, job))
        return listeners


class TaskFlowServiceController(object):

    def __init__(self, driver):
        self.driver = driver

    def run_poster(self, flow_factory, *args, wait=False, **kwargs):
        with self.driver.persistence_driver.get_persistence() as persistence:
            with self.driver.job_board(persistence) as job_board:
                job_id = uuidutils.generate_uuid()
                job_name = '-'.join([flow_factory.__name__, job_id])
                job_logbook = models.LogBook(job_name)
                flow_detail = models.FlowDetail(
                    job_name, job_id)
                job_details = {
                    'store': kwargs.pop('store')
                }
                job_logbook.add(flow_detail)
                persistence.get_connection().save_logbook(job_logbook)
                engines.save_factory_details(flow_detail, flow_factory,
                                             args, kwargs,
                                             backend=persistence)

                job_board.post(job_name, book=job_logbook,
                               details=job_details)
                if wait:
                    self._wait_for_job(job_board)

                return job_id

    def _wait_for_job(self, job_board):
        # Wait for job to its complete state
        for job in job_board.iterjobs():
            LOG.debug("Waiting for job %s to finish", job.name)
            job.wait()

    def run_conductor(self, name):
        with self.driver.persistence_driver.get_persistence() as persistence:
            with self.driver.job_board(persistence) as board:
                # Redis do not expire jobs by default, so jobs won't be resumed
                # with restart of controller. Add expiry for board and use
                # special listener.
                if (CONF.task_flow.jobboard_backend_driver ==
                        'redis_taskflow_driver'):
                    conductor = RedisDynamicLoggingConductor(
                        name, board, persistence=persistence,
                        engine=CONF.task_flow.engine)
                    board.claim = functools.partial(
                        board.claim,
                        expiry=CONF.task_flow.jobboard_expiration_time)
                else:
                    conductor = DynamicLoggingConductor(
                        name, board, persistence=persistence,
                        engine=CONF.task_flow.engine)

                conductor.run()
