# Copyright 2017 Catalyst IT Limited
#
#    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 time

from oslo_log import log as logging
import requests
import six
import tenacity

from qinling import context
from qinling.db import api as db_api
from qinling import status
from qinling.utils import constants

LOG = logging.getLogger(__name__)


def url_request(request_session, url, body=None):
    """Send request to a service url."""
    exception = None

    # Send ping request first to make sure the url works
    try:
        temp = url.split('/')
        temp[-1] = 'ping'
        ping_url = '/'.join(temp)
        r = tenacity.Retrying(
            wait=tenacity.wait_fixed(1),
            stop=tenacity.stop_after_attempt(30),
            reraise=True,
            retry=tenacity.retry_if_exception_type(IOError)
        )
        r.call(request_session.get, ping_url, timeout=(3, 3), verify=False)
    except Exception as e:
        LOG.exception(
            "Failed to request url %s, error: %s", ping_url, str(e)
        )
        return False, {'error': 'Function execution failed.'}

    for a in six.moves.xrange(10):
        res = None
        try:
            # Default execution max duration is 3min, could be configurable
            res = request_session.post(
                url, json=body, timeout=(3, 180), verify=False
            )
            return True, res.json()
        except requests.ConnectionError as e:
            exception = e
            time.sleep(1)
        except Exception as e:
            LOG.exception(
                "Failed to request url %s, error: %s", url, str(e)
            )
            if res:
                LOG.error("Response status: %s, content: %s",
                          res.status_code, res.content)

            return False, {'error': 'Function execution timeout.'}

    LOG.exception("Could not connect to function service. Reason: %s",
                  exception)

    return False, {'error': 'Internal service error.'}


def get_request_data(conf, function_id, version, execution_id, rlimit, input,
                     entry, trust_id, qinling_endpoint):
    """Prepare the request body should send to the worker."""
    ctx = context.get_ctx()

    if version == 0:
        download_url = (
            '%s/%s/functions/%s?download=true' %
            (qinling_endpoint.strip('/'), constants.CURRENT_VERSION,
             function_id)
        )
    else:
        download_url = (
            '%s/%s/functions/%s/versions/%s?download=true' %
            (qinling_endpoint.strip('/'), constants.CURRENT_VERSION,
             function_id, version)
        )

    data = {
        'execution_id': execution_id,
        'cpu': rlimit['cpu'],
        'memory_size': rlimit['memory_size'],
        'input': input,
        'function_id': function_id,
        'function_version': version,
        'entry': entry,
        'download_url': download_url,
        'request_id': ctx.request_id,
    }
    if conf.pecan.auth_enable:
        data.update(
            {
                'token': ctx.auth_token,
                'auth_url': conf.keystone_authtoken.www_authenticate_uri,
                'username': conf.keystone_authtoken.username,
                'password': conf.keystone_authtoken.password,
                'trust_id': trust_id
            }
        )

    return data


def db_set_execution_status(execution_id, execution_status, logs, res):
    db_api.update_execution(
        execution_id,
        {
            'status': execution_status,
            'logs': logs,
            'result': res
        }
    )


def finish_execution(execution_id, success, res, is_image_source=False):
    logs = ''
    if is_image_source:
        # If the function is created from docker image, the result is
        # direct output, here we convert to a dict to fit into the db
        # schema.
        res = {'output': res}
    else:
        # Execution log is only available for non-image source execution.
        logs = res.pop('logs', '')
        success = success and res.pop('success')

    LOG.debug(
        'Finished execution %s, success: %s', execution_id, success
    )
    db_set_execution_status(
        execution_id, status.SUCCESS if success else status.FAILED,
        logs, res
    )


def handle_execution_exception(execution_id, exc_str):
    # This method should be called from an exception handler
    LOG.exception(
        'Error running execution %s: %s', execution_id, exc_str
    )
    db_set_execution_status(
        execution_id, status.ERROR, '', {'error': 'Function execution failed.'}
    )
