# -*- coding: utf-8 -*-
# Copyright 2020 Google 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.

# This code is based on a file from Monorail:
# https://chromium.googlesource.com/infra/infra/+/master/appengine/monorail/framework/cloud_tasks_helpers.py

"""Helper functions for enqueueing tasks to Google Cloud Tasks.

Provides functions to create and enqueue background tasks, with local stubbing
support for development and testing environments.
"""

import json
import logging

import requests

import settings

if not settings.UNIT_TEST_MODE:
    from google.api_core import retry
    from google.cloud import tasks  # type: ignore


_client = None

# Default exponential backoff retry config for enqueueing, not to be confused
# with retry config for dispatching, which exists per queue.
_DEFAULT_RETRY = None
if not settings.UNIT_TEST_MODE:
    _DEFAULT_RETRY = retry.Retry(
        initial=0.1, maximum=1.6, multiplier=2, deadline=10
    )


class LocalCloudTasksClient(object):
    """We have no GCT server running locally, so hit the target synchronously."""

    def queue_path(self, project, location, queue):
        """Return a fully-qualified queue string."""
        # This is value is not actually used, but it might be good for debugging.
        return 'projects/{project}/locations/{location}/queues/{queue}'.format(
            project=project, location=location, queue=queue
        )

    def create_task(self, parent=None, task=None, **kwargs):
        """Immediately hit the target URL."""
        uri = task.get('app_engine_http_request').get('relative_uri')
        target_url = 'http://localhost:7777' + uri
        body = task.get('app_engine_http_request').get('body')
        handler_response = requests.request(
            'POST',
            target_url,
            data=body,
            allow_redirects=False,
            # This header can only be set on internal requests, not by users.
            headers={'X-AppEngine-QueueName': 'default'},
        )
        logging.info('Task handler status: %d', handler_response.status_code)


def _get_client():
    """Returns a cloud tasks client."""
    global _client
    if not _client:
        if settings.DEV_MODE:
            _client = LocalCloudTasksClient()
        else:
            _client = tasks.CloudTasksClient()
    return _client


def _make_task(handler_path, task_params):
    # Note: `default=str` is used to handle datetimes. Be cautious, as
    # this will also stringify any other non-serializable objects
    # passed in task_params.
    body_json = json.dumps(task_params, default=str)
    return {
        'app_engine_http_request': {
            'relative_uri': handler_path,
            'body': body_json.encode(),
        }
    }


def enqueue_task(handler_path, task_params, queue='default', **kwargs):
    """Enqueue a JSON task item for Google Cloud Tasks.

    Args:
      handler_path: Rooted path of the task handler.
      task_params: Task parameters dict.
      queue: A string indicating name of the queue to add task to.
      kwargs: Additional arguments to pass to cloud task client's create_task

    Returns:
      Successfully created Task object.
    """
    task = _make_task(handler_path, task_params)
    client = _get_client()
    parent = client.queue_path(
        settings.APP_ID, settings.CLOUD_TASKS_REGION, queue
    )

    logging.info('Enqueueing task to %s', parent)

    kwargs.setdefault('retry', _DEFAULT_RETRY)
    return client.create_task(parent=parent, task=task, **kwargs)
