# -*- coding: utf-8 -*-
#######
# actinia-core - an open source REST API for scalable, distributed, high
# performance processing of geographical data that uses GRASS GIS for
# computational tasks. For details, see https://actinia.mundialis.de/
#
# SPDX-FileCopyrightText: (c) 2016-2022 Sören Gebbert & mundialis GmbH & Co. KG
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
#######

"""
Kvdb connection interface
"""

import rq
from valkey import Valkey
from actinia_core.core.kvdb_user import kvdb_user_interface
from actinia_core.core.kvdb_api_log import kvdb_api_log_interface
from actinia_core.core.logging_interface import log
from .config import global_config
from .process_queue import enqueue_job as enqueue_job_local

__license__ = "GPL-3.0-or-later"
__author__ = "Sören Gebbert, Carmen Tawalika"
__copyright__ = "Copyright 2016-2022, Sören Gebbert & mundialis GmbH & Co. KG"
__maintainer__ = "mundialis GmbH & Co. KG"
__email__ = "info@mundialis.de"

job_queues = []
kvdb_conn = None


def connect(host, port, pw=None):
    """Connect all required kvdb interfaces that should be used
       in the main server process.

       These interfaces are connected here for performance reasons.
       The kvdb job queue is initialized here as well.

    Args:
        host (str): The hostname of the kvdb server
        port (str): The port of the kvdb server
        pw (str): The password of the kvdb server

    """
    kvdb_user_interface.connect(host, port, pw)
    kvdb_api_log_interface.connect(host, port, pw)


def disconnect():
    """Disconnect all required kvdb interfaces"""
    kvdb_user_interface.disconnect()
    kvdb_api_log_interface.disconnect()


def __create_job_queue(queue_name):
    """Create a single job queue for asynchronous processing

    Args:
        queue_name: The name of the queue

    """
    # Kvdb work queue and connection
    global kvdb_conn

    if not any(q.name == queue_name for q in job_queues):
        host = global_config.KVDB_QUEUE_SERVER_URL
        port = global_config.KVDB_QUEUE_SERVER_PORT
        password = global_config.KVDB_QUEUE_SERVER_PASSWORD

        kwargs = dict()
        kwargs["host"] = host
        kwargs["port"] = port
        if password and password is not None:
            kwargs["password"] = password
        kvdb_conn = Valkey(**kwargs)

        string = "Create queue %s with server %s:%s" % (queue_name, host, port)
        log.info(string)
        queue = rq.Queue(queue_name, connection=kvdb_conn)
        job_queues.append(queue)


def __enqueue_job_kvdb(queue, timeout, func, *args):
    """Enqueue a job in the job queues

    Args:
        func: The function to call from the subprocess
        *args: The function arguments
    """

    log.info("Enqueue job in queue %s" % queue.name)
    # Below timeout is defined in resource_base.pyL295.
    # If it is higher than 2147483647, it will be set to never expire.
    # Else it would raise an error:
    # int(process_time_limit * process_num_limit * 20)
    # which is 630720000000 and raises in worker:
    # OverflowError: Python int too large to convert to C int
    if timeout > 2147483647:
        timeout = -1  # never exprire
    ret = queue.enqueue(
        func,
        *args,
        job_timeout=timeout,
        ttl=global_config.KVDB_QUEUE_JOB_TTL,
        result_ttl=global_config.KVDB_QUEUE_JOB_TTL,
    )
    log.info(ret)


def enqueue_job(timeout, func, *args, queue_type_overwrite=None):
    """Write the provided function in a queue

    Args:
        timeout: The timeout of the process
        func: The function to call from the subprocess/worker
        *args: The function arguments
    """
    num_queues = global_config.NUMBER_OF_WORKERS
    queue_type = global_config.QUEUE_TYPE
    queue_name = "local"
    if queue_type_overwrite:
        queue_type = global_config.QUEUE_TYPE_OVERWRITE

    if queue_type == "per_job":
        resource_id = args[0].resource_id
        queue_name = "%s_%s" % (global_config.WORKER_QUEUE_PREFIX, resource_id)
        __create_job_queue(queue_name)
        for i in job_queues:
            if i.name == queue_name:
                args[0].set_queue_name(queue_name)
                __enqueue_job_kvdb(i, timeout, func, *args)

    elif queue_type == "per_user":
        user_id = args[0].user_id
        queue_name = "%s_%s" % (global_config.WORKER_QUEUE_PREFIX, user_id)
        # Run __create_job_queue every time.
        # If queue already exists, it does nothing.
        __create_job_queue(queue_name)
        for i in job_queues:
            if i.name == queue_name:
                args[0].set_queue_name(queue_name)
                __enqueue_job_kvdb(i, timeout, func, *args)

    elif queue_type == "kvdb":
        if job_queues == []:
            for i in range(num_queues):
                queue_name = "%s_%s" % (global_config.WORKER_QUEUE_PREFIX, i)
                __create_job_queue(queue_name)
        # The kvdb incr approach is used here
        # to chose for each job a different queue
        num = kvdb_conn.incr("actinia_worker_count", 1)
        current_queue = num % num_queues
        args[0].set_queue_name(job_queues[current_queue].name)
        __enqueue_job_kvdb(job_queues[current_queue], timeout, func, *args)

    elif queue_type == "local":
        # __enqueue_job_local(timeout, func, *args)
        args[0].set_queue_name(queue_name)
        enqueue_job_local(timeout, func, *args)
        return
        # Just in case the current process queue does not work
        # Then use the most simple solution by just starting the process
        from multiprocessing import Process

        p = Process(target=func, args=args)
        p.start()
