#!/usr/bin/env python3
# -*- 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/
#
# Copyright (c) 2016-2018 Soeren Gebbert and mundialis GmbH & Co. KG
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
#######

"""
Purpose: Command line program to manage actinia user
         entries in the Redis database
"""

from actinia_core.core.common.user import ActiniaUser
from actinia_core.core.redis_user import redis_user_interface
from actinia_core.core.common.config import Configuration
import argparse
import pprint
import sys

__license__    = "GPLv3"
__author__     = "Soeren Gebbert"
__copyright__  = "Copyright 2016-2019, Soen Gebbert and mundialis GmbH & Co. KG"
__maintainer__ = "Soeren Gebbert"
__email__      = "soerengebbert@googlemail.com"


def verify_password(username_or_token, password):
    """Verify the user name and password.

    Instead of a user name an authentication token
    can be provided.

    Args:
        username_or_token (str): The username or an authentication token
        password (str): The optional user password, not required in case of token

    Returns:
        bool: True if authorized or False if not

    """
    # first try to authenticate by token
    user = ActiniaUser.verify_auth_token(username_or_token)
    if not user:
        # try to authenticate with username/password
        user = ActiniaUser(user_id=username_or_token)
        if not user.exists() or not user.verify_password(password):
            return False
    return True


def set_user_credentials(user, args, method):
    """Set the user credentials

    Args:
        user (actinia.core.user.ActiniaUser()): The user object
        args: The command line arguments
        method (str): "create", "update", "update_add",
                      "update_rm", "update_rm_location"

    """

    if args.role:
        user.set_role(args.role)
    if args.cell_limit:
        user.set_cell_limit(args.cell_limit)
    if args.process_num_limit:
        user.set_process_num_limit(args.process_num_limit)
    if args.process_time_limit:
        user.set_process_time_limit(args.process_time_limit)
    if args.password:
        user.hash_password(args.password)

    if args.datasets:
        datasets = {}
        lm_list = args.datasets.split(",")

        for lm in lm_list:
            if "/" not in lm:
                sys.stderr.write("Wrong location mapset format for datasets\n")
                return

            if "/" in lm:
                location, mapset = lm.split("/")
            else:
                location = lm
                mapset = None

            if location not in datasets:
                datasets[location] = []

            datasets[location].append(mapset)

        if method == "update" or method == "create":
            user.set_accessible_datasets(datasets)
        if method == "update_add":
            for dataset in datasets:
                user.add_accessible_dataset(dataset, datasets[dataset])
        if method == "update_rm":
            for dataset in datasets:
                user.remove_mapsets_from_location(dataset, datasets[dataset])
        if method == "update_rm_location":
            for dataset in datasets:
                user.remove_location(dataset)

    if args.modules:
        modules = args.modules.split(",")
        if method == "update" or method == "create":
            user.set_accessible_modules(modules)
        if method == "update_add":
            user.add_accessible_modules(modules)
        if method == "update_rm":
            user.remove_accessible_modules(modules)


def main():
    """User management
    """
    parser = argparse.ArgumentParser(description='Manage actinia users in the Redis database.',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument("action", type=str, default="create",
                        choices=["create", "delete", "show", "list",
                                 "update", "update_add", "update_rm", "update_rm_location",
                                 "pwcheck"],
                        help="The action that should be performed:"
                             " * create: Create a user"
                             " * show:   Show the user credentials"
                             " * list:   List all users"
                             " * update: Update a user with the provided parameters"
                             " * update_add: Update a user with the provided parameters and add datasets or/and modules"
                             " * update_rm: Update a user with the provided parameters and remove mapsets or/and modules"
                             " * update_rm_location: Update a user with the provided parameters and remove locations or/and modules"
                             " * pwcheck: Check the password of the user"
                        )
    parser.add_argument("-s", "--server", type=str, required=False,
                        help="The host name of the redis server, default is the value from the actinia config file")
    parser.add_argument("-p", "--port", type=int, required=False,
                        help="The port of the redis server, default is the value from the actinia config file")
    parser.add_argument("-a", "--redis_password", type=str, required=False,
                        help="The password of the redis server, default is the value from the actinia config file or None")
    parser.add_argument("-u", "--user_id", type=str, required=False,
                        help="The user name")
    parser.add_argument("-g", "--user_group", type=str, required=False,
                        help="The name of the user group this user is associated with")
    parser.add_argument("-w", "--password", type=str, required=False,
                        help="The password")
    parser.add_argument("-r", "--role", type=str, required=False,
                        choices=["superadmin", "admin", "user", "guest"],
                        help="The user role")
    parser.add_argument("-c", "--cell_limit", type=int, required=False,
                        help="The maximum number of cells a user can process")
    parser.add_argument("-n", "--process_num_limit", type=int, required=False,
                        help="The maximum number of processes the user can execute in a single process chain")
    parser.add_argument("-t", "--process_time_limit", type=int, required=False,
                        help="The maximum number seconds a single process is allowed to run")
    parser.add_argument("-d", "--datasets", type=str, required=False,
                        help="The datasets the user is allowed to access. Format: location/mapset,location/mapset")
    parser.add_argument("-m", "--modules", type=str, required=False,
                        help="A list of modules the user is allowed to access. Format: module,module,module")

    args = parser.parse_args()

    conf = Configuration()
    try:
        conf.read()
    except:
        pass

    server = conf.REDIS_SERVER_URL
    port = conf.REDIS_SERVER_PORT
    if conf.REDIS_SERVER_PW:
        redis_password = conf.REDIS_SERVER_PW
    else:
        redis_password = None

    if args.server:
        server = args.server
    if args.port:
        port = args.port
    if args.redis_password:
        redis_password = args.redis_password
    redis_user_interface.connect(host=server, port=port, password=redis_password)

    ######################### CREATE ############################
    if args.action == "create":

        if args.user_id is None:
            sys.stderr.write("You need to provide a user id\n")
            return

        if args.user_group is None:
            sys.stderr.write("You need to provide a user group\n")
            return

        if args.password is None:
            sys.stderr.write("You need to provide a password\n")
            return

        if args.role is None:
            sys.stderr.write("You need to provide a user role\n")
            return

        user = ActiniaUser(user_id=args.user_id, user_group=args.user_group)

        if user.exists() == 1:
            sys.stderr.write("Unable to create the user <%s> the user exists\n"%args.user_id)
            return

        # Set the credentials
        set_user_credentials(user, args, args.action)

        if user.commit() is True:
            sys.stderr.write("Created user <%s> in group <%s>\n"%(args.user_id, args.user_group))
            sys.stdout.write(str(user))
            return
        else:
            sys.stderr.write("Unable to create the user <%s>\n"%args.user_id)
            return
    ######################### UPDATE ############################
    elif args.action == "update" or args.action == "update_add" or \
                    args.action == "update_rm" or args.action == "update_rm_location":

        if args.user_id is None:
            sys.stderr.write("You need to provide a user id\n")
            return

        user = ActiniaUser(user_id=args.user_id)

        if user.exists() == 0:
            sys.stderr.write("Unable to update the user <%s> the user does not exists\n"%args.user_id)
            return

        user.read_from_db()

        # Set the credentials
        set_user_credentials(user, args, args.action)

        if user.update() is True:
            sys.stderr.write("Updated user <%s>\n"%args.user_id)
            sys.stdout.write(str(user))
            return
        else:
            sys.stderr.write("Unable to update the user <%s>\n"%args.user_id)
            return

    ######################### DELETE ############################
    elif args.action == "delete":

        user = ActiniaUser(args.user_id)

        if user.exists() == 1:
            if user.delete() is True:
                sys.stderr.write("User <%s> deleted\n"%args.user_id)
                return
            else:
                sys.stderr.write("Unable to delete user <%s>\n"%args.user_id)
                return
        else:
            sys.stderr.write("User <%s> does not exist\n"%args.user_id)

    ######################### SHOW ##############################
    elif args.action == "show":

        user = ActiniaUser(args.user_id)

        if user.exists() == 1:
            sys.stdout.write(str(user))
        else:
            sys.stderr.write("User <%s> does not exist\n"%args.user_id)

    ######################### LIST ##############################
    elif args.action == "list":

        user = ActiniaUser(args.user_id)
        pprint.pprint(user.list_all_users())

    ######################### PWCHECK ###########################
    elif args.action == "pwcheck":

        if args.user_id is None:
            sys.stderr.write("You need to provide a user id\n")
            return

        if args.password is None:
            sys.stderr.write("You need to provide a password\n")
            return

        if verify_password(args.user_id, args.password) is False:
            sys.stderr.write("Wrong username or password\n")
        else:
            sys.stderr.write("Password and user name are correct\n")


if __name__ == '__main__':
    main()
