"""
This module contains the signal handlers of the :py:mod:`osusers` app.

The module starts Celery_ tasks.

.. _Celery: https://www.celeryproject.org/

"""
from __future__ import absolute_import

import logging

from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver

from fileservertasks.tasks import (
    delete_file_mail_userdir,
    delete_file_sftp_userdir_chained,
    set_file_ssh_authorized_keys,
    setup_file_mail_userdir_chained,
    setup_file_sftp_userdir_chained,
)
from ldaptasks.tasks import (
    add_ldap_user_to_group,
    create_ldap_group,
    create_ldap_user,
    delete_ldap_group,
    delete_ldap_user_chained,
    remove_ldap_user_from_group,
    set_ldap_user_password,
)
from taskresults.models import TaskResult

from .models import AdditionalGroup, Group, SshPublicKey, User, password_set

_LOGGER = logging.getLogger(__name__)


@receiver(password_set, sender=User)
def handle_user_password_set(sender, instance, password, **kwargs):
    """
    Handles password changes on :py:class:`User <osusers.models.User>`
    instances.

    :param sender: sender of the signal
    :param instance: User instance
    :param str password: the new password

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler" ];
            B [ label = "set ldap user password", color = "Wheat",
                description = ":py:func:`set_ldap_user_password()
                <ldaptasks.tasks.set_ldap_user_password>` called with
                username and password, returning :py:const:`True` if the
                password has been set" ];
        }
    """
    taskresult = TaskResult.objects.create_task_result(
        "handle_user_password_set",
        set_ldap_user_password.s(instance.username, password),
    )
    _LOGGER.info(
        "LDAP password change has been requested in task %s", taskresult.task_id
    )


@receiver(post_save, sender=Group)
def handle_group_created(sender, instance, created, **kwargs):
    """
    Handles post creation actions on :py:class:`Group <osusers.models.Group>`
    instances.

    :param sender: sender of the signal
    :param instance: Group instance
    :param bool created: whether the instance has just been created

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler" ];
            B [ label = "create ldap group", color = "Wheat",
                description = ":py:func:`create_ldap_group()
                <ldaptasks.tasks.create_ldap_group>` called with groupname,
                gid and description, returning group DN" ];
        }

    """
    if created:
        taskresult = TaskResult.objects.create_task_result(
            "handle_group_created",
            create_ldap_group.s(instance.groupname, instance.gid, instance.descr),
        )
        _LOGGER.info(
            "LDAP group creation has been requested in task %s", taskresult.task_id
        )
    _LOGGER.debug("group %s has been %s", instance, created and "created" or "updated")


@receiver(post_save, sender=User)
def handle_user_created(sender, instance, created, **kwargs):
    """
    Handles post creation actions on :py:class:`User <osusers.models.User>`
    instances.

    :param sender: sender of the signal
    :param instance: User instance
    :param bool created: whether the instance has just bean created

    This signal handler starts a chain of Celery_ tasks.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B -> C -> D;
            B -> C [folded];

            A [ label = "", shape = beginpoint,
                description = "this signal handler" ];
            B [ label = "create ldap user", color = "Wheat",
                description = ":py:func:`create_ldap_user()
                <ldaptasks.tasks.create_ldap_user>` called with username, uid,
                gid, gecos, homeidr, shell, :py:const:`None`, returning
                username" ];
            C [ label = "setup file sftp userdir", color = "LightGreen",
                description = ":py:func:`setup_file_sftp_userdir_chained()
                <fileservertasks.tasks.setup_file_sftp_userdir_chained>`
                called with the result of create ldap user task, returning a
                dictionary containing username and sftp_directory"];
            D [ label = "setup file mail userdir", color = "LightGreen",
                description = ":py:func:`setup_file_mail_userdir_chained()
                <fileservertasks.tasks.setup_file_mail_userdir_chained>` called
                with result of setup file sftp userdir task, returning
                dictionary containing username, sftp_directory and
                mail_directory" ];
        }

    """
    if created:
        task_ldap_1 = create_ldap_user.s(
            instance.username,
            instance.uid,
            instance.group.gid,
            instance.gecos,
            instance.homedir,
            instance.shell,
            None,
        ).set(queue="ldap")
        task_file_1 = setup_file_sftp_userdir_chained.s().set(queue="file")
        task_file_2 = setup_file_mail_userdir_chained.s().set(queue="file")

        chain = task_ldap_1 | task_file_1 | task_file_2

        task_result = TaskResult.objects.create_task_result(
            "handle_user_created", chain
        )
        _LOGGER.info(
            "LDAP user creation has been requested in task %s", task_result.task_id
        )
    _LOGGER.debug("user %s has been %s", instance, created and "created" or "updated")


@receiver(post_save, sender=AdditionalGroup)
def handle_user_added_to_group(sender, instance, created, **kwargs):
    """
    Handles post creation actions on :py:class:`AdditionalGroup
    <osusers.models.AdditionalGroup>` instances.

    :param sender: sender of the signal
    :param instance: AdditionalGroup instance
    :param bool created: whether the instance has just bean created

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler" ];
            B [ label = "add ldap user to group", color = "Wheat",
                description = ":py:func:`add_ldap_user_to_group()
                <ldaptasks.tasks.add_ldap_user_to_group>` called with username
                and groupname, returning :py:const:`True` if the user has been
                added to the group" ];
        }

    """
    if created:
        taskresult = TaskResult.objects.create_task_result(
            "handle_user_added_to_group",
            add_ldap_user_to_group.s(instance.user.username, instance.group.groupname),
        )
        _LOGGER.info(
            "Adding user to LDAP group has been requested in task %s",
            taskresult.task_id,
        )


@receiver(post_save, sender=SshPublicKey)
@receiver(post_delete, sender=SshPublicKey)
def handle_ssh_keys_changed(sender, instance, **kwargs):
    """
    Handles changes to :py:class:`SshPublicKey <osuses.models.SshPublicKey>`
    instances related to a user.

    :param sender: sender of the signal
    :param instance: SshPublicKey instance

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler" ];
            B [ label = "set file ssh authorized_keys", color = "LightGreen",
                description = ":py:func:`set_file_ssh_authorized_keys()
                <fileservertasks.tasks.set_file_ssh_authorized_keys>` called
                with username and the corresponding list of keys, returning the
                path of the ssh_authorized_keys_file" ];
        }

    """
    sig = set_file_ssh_authorized_keys.s(
        instance.user.username,
        [str(key) for key in SshPublicKey.objects.filter(user=instance.user)],
    )
    taskresult = TaskResult.objects.create_task_result("handle_ssh_keys_changed", sig)
    _LOGGER.info("Change of SSH keys has been requested in task %s", taskresult.task_id)


@receiver(post_delete, sender=Group)
def handle_group_deleted(sender, instance, **kwargs):
    """
    Handles cleanup actions to be done after deletion of a :py:class:`Group
    <osusers.models.Group>` instance.

    :param sender: sender of the signal
    :param instance: Group instance

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler"
            ];
            B [ label = "delete ldap group", color = "Wheat",
                description = ":py:func:`delete_ldap_group()
                <ldaptasks.tasks.delete_ldap_group>` called with groupname,
                returning :py:const:`True` if the group has been deleted" ];
        }

    """
    taskresult = TaskResult.objects.create_task_result(
        "handle_group_deleted", delete_ldap_group.s(instance.groupname)
    )
    _LOGGER.info(
        "LDAP group deletion has been requested in task %s", taskresult.task_id
    )


@receiver(post_delete, sender=User)
def handle_user_deleted(sender, instance, **kwargs):
    """
    Handles cleanup actions to be done after deletion of a :py:class:`User
    <osusers.models.User>` instance.

    :param sender: sender of the signal
    :param instance: User instance

    This signal handler starts a chain of Celery_ tasks.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B -> C -> D;
            B -> C [folded];

            A [ label = "", shape = beginpoint,
                description = "this signal handler"
            ];
            B [ label = "delete file mail userdir", color = "LightGreen",
                description = ":py:func:`delete_file_mail_userdir()
                <fileservertasks.tasks.delete_file_mail_userdir>` called with
                username, returning a dictionary containing the username and
                the deleted mail_directory" ];
            C [ label = "delete file sftp userdir", color = "LightGreen",
                description = ":py:func:`delete_file_sftp_userdir_chained()
                <fileservertasks.tasks.delete_file_sftp_userdir_chained>`
                called with the result of delete mail userdir, returning
                dictionary containing username, deleted mail_directory and
                deleted sftp_directory" ];
            D [ label = "delete ldap user", color = "Wheat",
                description = ":py:func:`delete_ldap_user_chained()
                <ldaptasks.tasks.delete_ldap_user_chained>` called with the
                result of delete file sftp userdir and adding the deleted user
                DN to the result" ];
        }

    """
    file_task_1 = delete_file_mail_userdir.s(instance.username).set(queue="file")
    file_task_2 = delete_file_sftp_userdir_chained.s().set(queue="file")
    ldap_task_1 = delete_ldap_user_chained.s().set(queue="ldap")

    chain = file_task_1 | file_task_2 | ldap_task_1
    _LOGGER.debug("chain signature %s", chain)
    task_result = TaskResult.objects.create_task_result("handle_user_deleted", chain)

    _LOGGER.info(
        "LDAP user deletion has been requested in task %s", task_result.task_id
    )


@receiver(post_delete, sender=AdditionalGroup)
def handle_user_removed_from_group(sender, instance, **kwargs):
    """
    Handles cleanup actions to be done after removing a user from a group by
    deleting the :py:class:`AdditionalGroup <osusers.models.AdditionalGroup>`
    instance.

    :param sender: sender of the signal
    :param instance: AdditionalGroup instance

    This signal handler starts a Celery_ task.

    .. blockdiag::
        :desctable:

        blockdiag {
            node_width = 200;

            A -> B;

            A [ label = "", shape = beginpoint,
                description = "this signal handler"
            ];
            B [ label = "remove ldap user from group", color = "Wheat",
                description = ":py:func:`remove_ldap_user_from_group()
                <ldaptasks.tasks.remove_ldap_user_from_group>` called with
                username and groupname, returning :py:const:`True` if the user
                has been a member of the group and has been removed from the
                group"
            ];
        }

    """
    taskresult = TaskResult.objects.create_task_result(
        "handle_user_removed_from_group",
        remove_ldap_user_from_group.s(instance.user.username, instance.group.groupname),
    )
    _LOGGER.info(
        "Removing user from LDAP group has been requested in task %s",
        taskresult.task_id,
    )
