# Copyright (C) 2013-2016 DNAnexus, Inc.
#
# This file is part of dx-toolkit (DNAnexus platform client libraries).
#
#   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 submodule contains the callables (and their helpers) that are called by
the org-based commands of the dx command-line client.
'''
from __future__ import print_function, unicode_literals, division, absolute_import

import dxpy
from . import try_call, prompt_for_yn, INTERACTIVE_CLI
from .parsers import process_find_by_property_args, process_phi_param
from ..exceptions import (DXCLIError, err_exit)
from dxpy.utils.printing import (fill, DELIMITER, format_find_results)
import json


def get_user_id(user_id_or_username):
    """Gets the user ID based on the value `user_id_or_username` specified on
    the command-line, being extra lenient and lowercasing the value in all
    cases.
    """
    user_id_or_username = user_id_or_username.lower()
    if not user_id_or_username.startswith("user-"):
        user_id = "user-" + user_id_or_username.lower()
    else:
        user_id = user_id_or_username
    return user_id


def get_org_invite_args(user_id, args):
    """
    Used by:
        - `dx new user`
        - `dx add member`

    PRECONDITION:
        - If /org-x/invite is being called in conjunction with /user/new, then
          `_validate_new_user_input()` has been called on `args`; otherwise,
          the parser must perform all the basic input validation.
    """
    org_invite_args = {"invitee": user_id}
    org_invite_args["level"] = args.level
    if "set_bill_to" in args and args.set_bill_to is True:
        # /org-x/invite is called in conjunction with /user/new.
        org_invite_args["allowBillableActivities"] = True
    else:
        org_invite_args["allowBillableActivities"] = args.allow_billable_activities
    org_invite_args["appAccess"] = args.app_access
    org_invite_args["projectAccess"] = args.project_access
    org_invite_args["suppressEmailNotification"] = args.no_email
    return org_invite_args


def add_membership(args):
    user_id = get_user_id(args.username_or_user_id)

    try:
        dxpy.api.org_find_members(args.org_id, {"id": [user_id]})["results"][0]
    except:
        pass
    else:
        raise DXCLIError("Cannot add a user who is already a member of the org. To update an existing member's permissions, use 'dx update member'")

    dxpy.api.org_invite(args.org_id, get_org_invite_args(user_id, args))

    if args.brief:
        print("org-" + args.org_id)
    else:
        print(fill("Invited {u} to {o}".format(u=user_id, o=args.org_id)))


def _get_org_remove_member_args(args):
    remove_member_args = {
        "user": get_user_id(args.username_or_user_id),
        "revokeProjectPermissions": args.revoke_project_permissions,
        "revokeAppPermissions": args.revoke_app_permissions}
    return remove_member_args


def remove_membership(args):
    user_id = get_user_id(args.username_or_user_id)

    try:
        dxpy.api.org_find_members(args.org_id, {"id": [user_id]})["results"][0]
    except IndexError:
        raise DXCLIError("Cannot remove a user who is not a member of the org")

    confirmed = not args.confirm
    if not confirmed:
        # Request interactive confirmation.
        print(fill("WARNING: About to remove {u} from {o}; project permissions will{rpp} be removed and app permissions will{rap} be removed".format(
            u=user_id, o=args.org_id,
            rpp="" if args.revoke_project_permissions else " not",
            rap="" if args.revoke_app_permissions else " not")))

        if prompt_for_yn("Please confirm"):
            confirmed = True

    if confirmed:
        result = dxpy.api.org_remove_member(args.org_id,
                                            _get_org_remove_member_args(args))
        if args.brief:
            print(result["id"])
        else:
            print(fill("Removed {u} from {o}".format(u=user_id, o=args.org_id)))
            print(fill("Removed {u} from the following projects:".format(u=user_id)))
            if len(result["projects"].keys()) != 0:
                for project_id in result["projects"].keys():
                    print("\t{p}".format(p=project_id))
            else:
                print("\tNone")
            print(fill("Removed {u} from the following apps:".format(u=user_id)))
            if len(result["apps"].keys()) != 0:
                for app_id in result["apps"].keys():
                    print("\t{a}".format(a=app_id))
            else:
                print("\tNone")
    else:
        print(fill("Aborting removal of {u} from {o}".format(u=user_id, o=args.org_id)))


def _get_org_set_member_access_args(args, current_level):
    user_id = get_user_id(args.username_or_user_id)
    org_set_member_access_input = {user_id: {}}

    if args.level is not None:
        org_set_member_access_input[user_id]["level"] = args.level
    else:
        org_set_member_access_input[user_id]["level"] = current_level

    admin_to_member = args.level == "MEMBER" and current_level == "ADMIN"

    if args.allow_billable_activities is not None:
        org_set_member_access_input[user_id]["allowBillableActivities"] = args.allow_billable_activities == "true"
    elif admin_to_member:
        org_set_member_access_input[user_id]["allowBillableActivities"] = False

    if args.app_access is not None:
        org_set_member_access_input[user_id]["appAccess"] = args.app_access == "true"
    elif admin_to_member:
        org_set_member_access_input[user_id]["appAccess"] = True

    if args.project_access is not None:
        org_set_member_access_input[user_id]["projectAccess"] = args.project_access
    elif admin_to_member:
        org_set_member_access_input[user_id]["projectAccess"] = "CONTRIBUTE"

    return org_set_member_access_input


def update_membership(args):
    user_id = get_user_id(args.username_or_user_id)

    try:
        member_access = dxpy.api.org_find_members(args.org_id, {"id": [user_id]})["results"][0]
    except IndexError:
        raise DXCLIError("Cannot update a user who is not a member of the org")

    current_level = member_access["level"]

    result = dxpy.api.org_set_member_access(args.org_id,
                                            _get_org_set_member_access_args(args,
                                                                            current_level))
    if args.brief:
        print(result["id"])
    else:
        print(fill("Updated membership of {u} in {o}".format(u=user_id, o=args.org_id)))


def _get_find_orgs_args(args):
    find_orgs_input = {"level": args.level}

    if args.with_billable_activities is not None:
        find_orgs_input["allowBillableActivities"] = args.with_billable_activities

    if not args.brief:
        find_orgs_input["describe"] = True

    return {"query": find_orgs_input}


def find_orgs(args):
    res_iter = dxpy.find_orgs(_get_find_orgs_args(args)["query"])

    if args.json:
        print(json.dumps(list(res_iter)))
    elif args.brief:
        for res in res_iter:
            print(res["id"])
    else:
        for res in res_iter:
            print("{o}{d1}{n}".format(
                o=res["id"],
                d1=(DELIMITER(args.delimiter) if args.delimiter else " : "),
                n=res["describe"]["name"]
            ))


def org_find_members(args):
    results = try_call(dxpy.org_find_members, org_id=args.org_id, level=args.level, describe=(not args.brief))
    format_find_results(args, results)


def new_org(args):
    if args.name is None and INTERACTIVE_CLI:
        args.name = input("Enter descriptive name for new org: ")

    if args.name is None:
        err_exit("No org name supplied and input is not interactive.")

    org_new_input = {"handle": args.handle, "name": args.name,
                     "policies": {"memberListVisibility": args.member_list_visibility,
                                  "restrictProjectTransfer": args.project_transfer_ability}}

    resp = try_call(dxpy.api.org_new, org_new_input)
    if args.brief:
        print(resp['id'])
    else:
        print('Created new org called "' + args.name + '" (' + resp['id'] + ')')


def _get_org_update_args(args):
    org_update_inputs = {}

    if args.name is not None:
        org_update_inputs["name"] = args.name
    if args.saml_idp is not None:
        org_update_inputs["samlIdP"] = args.saml_idp

    if any(policy not in (None, False) for policy in (args.member_list_visibility, args.project_transfer_ability, args.enable_job_reuse, args.disable_job_reuse)) or args.detailed_job_metrics_collect_default is not None:
        org_update_inputs["policies"] = {}
    if args.member_list_visibility is not None:
        org_update_inputs["policies"]["memberListVisibility"] = args.member_list_visibility
    if args.project_transfer_ability is not None:
        org_update_inputs["policies"]["restrictProjectTransfer"] = args.project_transfer_ability        
    if args.enable_job_reuse == True:
        org_update_inputs["policies"]["jobReuse"] = True
    elif args.disable_job_reuse == True:
        org_update_inputs["policies"]["jobReuse"] = False
    if args.detailed_job_metrics_collect_default is not None:
        org_update_inputs["policies"]["detailedJobMetricsCollectDefault"] = args.detailed_job_metrics_collect_default == 'true'
    if args.job_logs_forwarding_json is not None:
        try: 
            org_update_inputs["jobLogsForwarding"] = json.loads(args.job_logs_forwarding_json)
        except:
            err_exit("Invalid JSON input for --job-logs-forwarding-json")

    return org_update_inputs


def update_org(args):
    org_update_inputs = _get_org_update_args(args)
    res = try_call(dxpy.api.org_update, args.org_id, org_update_inputs)
    if args.brief:
        print(res["id"])
    else:
        print(fill("Updated {o}".format(o=res["id"])))


def org_find_projects(args):
    try_call(process_find_by_property_args, args)
    try_call(process_phi_param, args)
    try:
        results = dxpy.org_find_projects(org_id=args.org_id, name=args.name, name_mode='glob',
                                         ids=args.ids, properties=args.properties, tags=args.tag,
                                         describe=(not args.brief),
                                         public=args.public,
                                         created_after=args.created_after,
                                         created_before=args.created_before,
                                         region=args.region,
                                         containsPHI=args.containsPHI)
    except:
        err_exit()
    format_find_results(args, results)


def org_find_apps(args):
    try:
        results = dxpy.org_find_apps(org_id=args.org_id,
                                     name=args.name,
                                     name_mode='glob',
                                     describe=(not args.brief),
                                     created_after=args.created_after,
                                     created_before=args.created_before)
    except:
        err_exit()
    format_find_results(args, results)
