import json
import logging

from google.cloud import tasks_v2 as tasks
from .project import ProjectReference
from .exceptions import ProjectReferenceNotDefinedException


class QueueUtil:
    """Auxiliary class for Google Cloud Tasks API"""

    QUEUE_HEADERS = ['X-Appengine-User-Ip', 'X-Appengine-Queuename', 'X-Appengine-Taskname',
                     'X-Appengine-Taskretrycount', 'X-Appengine-Taskexecutioncount', 'X-Appengine-Tasketa']

    def __init__(self, queue_name, project=None, project_id=None, location=None):
        self.project = project if project else ProjectReference(project_id, location)
        self.client = tasks.CloudTasksClient()
        self.queue_name = queue_name
        self.queue_path = self.client.queue_path(self.project.project_id, self.project.location, self.queue_name)

    def create_queue(self, queue_def: dict):
        """
        Creates a cron job in Google Cloud Scheduler

        :param queue_def: Queue defintion. See: google.cloud.tasks_v2.types.queue_pb2.Queue
        :raises ProjectReferenceNotDefinedException
        """
        if not self.project:
            raise ProjectReferenceNotDefinedException()

        parent = self.client.location_path(self.project.project_id, self.project.location)

        queue_name = f'projects/{self.project.project_id}' \
                     f'/locations/{self.project.location}' \
                     f'/queues/{self.queue_name}'
        queue_def['name'] = queue_name

        if 'rate_limits' not in queue_def:
            queue_def['rate_limits'] = {
                "max_dispatches_per_second": 5,
                "max_burst_size": 10
            }

        self.client.create_queue(parent, queue_def)

    def enqueue(self, request_method, request_relative_uri, request_data=None):
        encoded_payload = (
            json.dumps(request_data).encode()
            if request_data is not None
            else None
        )

        queue = {
            'app_engine_http_request': {
                'http_method': request_method,
                'relative_uri': request_relative_uri
            }
        }

        if encoded_payload is not None:
            queue['app_engine_http_request']['body'] = encoded_payload

        self.client.create_task(self.queue_path, queue)

        # remove body to log
        if 'body' in queue['app_engine_http_request']:
            queue['app_engine_http_request'].pop('body')

        logging.info(f'Task enqeued: {queue}')

    def pause(self):
        return self.client.pause_queue(self.queue_path)

    def resume(self):
        return self.client.resume_queue(self.queue_path)

    def list_tasks(self):
        return [t for t in self.client.list_tasks(self.queue_path)]

    @classmethod
    def is_queue_request(cls, queue_name, request):
        """
        Verify if a flask request is generated by GCloud Tasks

        A push task HTTP request has special headers set by App Engine, which contain task-specific
        information your handler can use.

        If these headers are present in an external user request to your app, they are stripped and replaced.
        The sole exception is for requests from logged-in administrators of the application, who are allowed
        to set headers for testing purposes.

        Reference:
        https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/creating-handlers

        :param queue_name:  Queue name
        :param request:     Flask request object
        :return:            If the request was created by a google cloud tasks job
        """
        headers = dict(t for t in request.headers if t[0] in cls.QUEUE_HEADERS)
        if set(cls.QUEUE_HEADERS) == headers.keys():
            return headers['X-Appengine-Queuename'] == queue_name

        return False
