import sys

import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi import errors as gapi_errors
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations


def GroupIsAbuseOrPostmaster(emailAddr):
    return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')


def mapGroupEmailForSettings(emailAddr):
  return emailAddr.replace('/', '%2F')


def create():
    cd = gapi_directory.build()
    body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)}
    gs_get_before_update = got_name = False
    verifyNotInvitable = False
    i = 4
    gs_body = {}
    gs = None
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'name':
            body['name'] = sys.argv[i + 1]
            got_name = True
            i += 2
        elif myarg == 'description':
            description = sys.argv[i + 1].replace('\\n', '\n')
            # The Directory API Groups insert method can not handle any of
            # these characters ('\n<>=') in the description field. If any of
            # these characters are present, use the Group Settings API to set
            # the description.
            for c in '\n<>=':
                if description.find(c) != -1:
                    gs_body['description'] = description
                    if not gs:
                        gs = gam.buildGAPIObject('groupssettings')
                        gs_object = gs._rootDesc
                    break
            else:
                body['description'] = description
            i += 2
        elif myarg == 'getbeforeupdate':
            gs_get_before_update = True
            i += 1
        elif myarg == 'verifynotinvitable':
            verifyNotInvitable = True
            i += 1
        else:
            if not gs:
                gs = gam.buildGAPIObject('groupssettings')
                gs_object = gs._rootDesc
            getGroupAttrValue(myarg, sys.argv[i + 1], gs_object, gs_body,
                              'create')
            i += 2
    if not got_name:
        body['name'] = body['email']
    if (verifyNotInvitable and
        gapi_cloudidentity_userinvitations.get_is_invitable_user(body['email'])):
        sys.stderr.write(f'Group not created, {body["email"]} is an unmanaged account\n')
        sys.exit(51)
    print(f'Creating group {body["email"]}')
    gapi.call(cd.groups(), 'insert', body=body, fields='email')
    if gs and not GroupIsAbuseOrPostmaster(body['email']):
        if gs_get_before_update:
            current_settings = gapi.call(
                gs.groups(),
                'get',
                retry_reasons=[
                    gapi_errors.ErrorReason.SERVICE_LIMIT,
                    gapi_errors.ErrorReason.NOT_FOUND
                ],
                groupUniqueId=mapGroupEmailForSettings(body['email']),
                fields='*')
            if current_settings is not None:
                gs_body = dict(
                    list(current_settings.items()) + list(gs_body.items()))
        if gs_body:
            gapi.call(gs.groups(),
                      'update',
                      groupUniqueId=mapGroupEmailForSettings(body['email']),
                      retry_reasons=[
                          gapi_errors.ErrorReason.SERVICE_LIMIT,
                          gapi_errors.ErrorReason.NOT_FOUND
                      ],
                      body=gs_body)


def delete():
    cd = gapi_directory.build()
    group = gam.normalizeEmailAddressOrUID(sys.argv[3])
    print(f'Deleting group {group}')
    gapi.call(cd.groups(), 'delete', groupKey=group)


def deleteUserFromGroups(users):
    cd = gapi_directory.build()
    for user in users:
        user_groups = gapi.get_all_pages(cd.groups(),
                                         'list',
                                         'groups',
                                         userKey=user,
                                         fields='groups(id,email)')
        jcount = len(user_groups)
        print(f'{user} is in {jcount} groups')
        j = 0
        for user_group in user_groups:
            j += 1
            group_email = user_group['email']
            current_count = gam.currentCount(j, jcount)
            print(f' removing {user} from {group_email} {current_count}')
            gapi.call(cd.members(),
                      'delete',
                      soft_errors=True,
                      groupKey=user_group['id'],
                      memberKey=user)
        print('')


def exists(cd, group, i=0, count=0):
    group = gam.normalizeEmailAddressOrUID(group)
    try:
        return gapi.call(cd.groups(),
                         'get',
                         throw_reasons=gapi_errors.GROUP_GET_THROW_REASONS,
                         retry_reasons=gapi_errors.GROUP_GET_RETRY_REASONS,
                         groupKey=group,
                         fields='email')['email']
    except (gapi_errors.GapiGroupNotFoundError,
            gapi_errors.GapiDomainNotFoundError,
            gapi_errors.GapiDomainCannotUseApisError,
            gapi_errors.GapiForbiddenError, gapi_errors.GapiBadRequestError):
        gam.entityUnknownWarning('Group', group, i, count)
        return None


def info(group_name=None):
    cd = gapi_directory.build()
    gs = gam.buildGAPIObject('groupssettings')
    getAliases = getUsers = True
    getGroups = False
    if group_name is None:
        group_name = gam.normalizeEmailAddressOrUID(sys.argv[3])
        i = 4
    else:
        i = 3
    while i < len(sys.argv):
        myarg = sys.argv[i].lower()
        if myarg == 'nousers':
            getUsers = False
            i += 1
        elif myarg == 'noaliases':
            getAliases = False
            i += 1
        elif myarg == 'groups':
            getGroups = True
            i += 1
        elif myarg in [
                'nogroups', 'nolicenses', 'nolicences', 'noschemas', 'schemas',
                'userview'
        ]:
            i += 1
            if myarg == 'schemas':
                i += 1
        else:
            controlflow.invalid_argument_exit(myarg, 'gam info group')
    basic_info = gapi.call(cd.groups(), 'get', groupKey=group_name)
    settings = {}
    if not GroupIsAbuseOrPostmaster(basic_info['email']):
        try:
            settings = gapi.call(
                gs.groups(),
                'get',
                throw_reasons=[gapi_errors.ErrorReason.AUTH_ERROR],
                retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
                groupUniqueId=mapGroupEmailForSettings(basic_info['email'])
            )  # Use email address retrieved from cd since GS API doesn't support uid
            if settings is None:
                settings = {}
        except gapi_errors.GapiAuthErrorError:
            pass
    print('')
    print('Group Settings:')
    for key, value in list(basic_info.items()):
        if (key in ['kind', 'etag']) or ((key == 'aliases') and
                                         (not getAliases)):
            continue
        if isinstance(value, list):
            print(f' {key}:')
            for val in value:
                print(f'  {val}')
        else:
            print(f' {key}: {value}')
    for key, value in list(settings.items()):
        if key in ['kind', 'etag', 'description', 'email', 'name']:
            continue
        print(f' {key}: {value}')
    if getGroups:
        groups = gapi.get_all_pages(cd.groups(),
                                    'list',
                                    'groups',
                                    userKey=basic_info['email'],
                                    fields='nextPageToken,groups(name,email)')
        if groups:
            print(f'Groups: ({len(groups)})')
            for groupm in groups:
                print(f'  {groupm["name"]}: {groupm["email"]}')
    if getUsers:
        members = gapi.get_all_pages(
            cd.members(),
            'list',
            'members',
            groupKey=group_name,
            fields='nextPageToken,members(email,id,role,type)')
        print('Members:')
        for member in members:
            print(
                f' {member.get("role", ROLE_MEMBER).lower()}: {member.get("email", member["id"])} ({member["type"].lower()})'
            )
        print(f'Total {len(members)} users in group')


def info_member():
    cd = gapi_directory.build()
    memberKey = gam.normalizeEmailAddressOrUID(sys.argv[3])
    groupKey = gam.normalizeEmailAddressOrUID(sys.argv[4])
    member_info = gapi.call(cd.members(),
                            'get',
                            memberKey=memberKey,
                            groupKey=groupKey)
    display.print_json(member_info)


GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
    'admincreated': ['adminCreated', 'Admin_Created'],
    'aliases': [
        'aliases', 'Aliases', 'nonEditableAliases', 'NonEditableAliases'
    ],
    'description': ['description', 'Description'],
    'directmemberscount': ['directMembersCount', 'DirectMembersCount'],
    'email': ['email', 'Email'],
    'id': ['id', 'ID'],
    'name': ['name', 'Name'],
}

GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
    'allowexternalmembers':
        'allowExternalMembers',
    'allowgooglecommunication':
        'allowGoogleCommunication',
    'allowwebposting':
        'allowWebPosting',
    'archiveonly':
        'archiveOnly',
    'customfootertext':
        'customFooterText',
    'customreplyto':
        'customReplyTo',
    'defaultmessagedenynotificationtext':
        'defaultMessageDenyNotificationText',
    'defaultsender':
        'defaultSender',
    'enablecollaborativeinbox':
        'enableCollaborativeInbox',
    'favoriterepliesontop':
        'favoriteRepliesOnTop',
    'gal':
        'includeInGlobalAddressList',
    'includecustomfooter':
        'includeCustomFooter',
    'includeinglobaladdresslist':
        'includeInGlobalAddressList',
    'isarchived':
        'isArchived',
    'memberscanpostasthegroup':
        'membersCanPostAsTheGroup',
    'messagemoderationlevel':
        'messageModerationLevel',
    'primarylanguage':
        'primaryLanguage',
    'replyto':
        'replyTo',
    'sendmessagedenynotification':
        'sendMessageDenyNotification',
    'showingroupdirectory':
        'showInGroupDirectory',
    'spammoderationlevel':
        'spamModerationLevel',
    'whocanadd':
        'whoCanAdd',
    'whocanapprovemembers':
        'whoCanApproveMembers',
    'whocanapprovemessages':
        'whoCanApproveMessages',
    'whocanassigntopics':
        'whoCanAssignTopics',
    'whocanassistcontent':
        'whoCanAssistContent',
    'whocanbanusers':
        'whoCanBanUsers',
    'whocancontactowner':
        'whoCanContactOwner',
    'whocandeleteanypost':
        'whoCanDeleteAnyPost',
    'whocandeletetopics':
        'whoCanDeleteTopics',
    'whocandiscovergroup':
        'whoCanDiscoverGroup',
    'whocanenterfreeformtags':
        'whoCanEnterFreeFormTags',
    'whocanhideabuse':
        'whoCanHideAbuse',
    'whocaninvite':
        'whoCanInvite',
    'whocanjoin':
        'whoCanJoin',
    'whocanleavegroup':
        'whoCanLeaveGroup',
    'whocanlocktopics':
        'whoCanLockTopics',
    'whocanmaketopicssticky':
        'whoCanMakeTopicsSticky',
    'whocanmarkduplicate':
        'whoCanMarkDuplicate',
    'whocanmarkfavoritereplyonanytopic':
        'whoCanMarkFavoriteReplyOnAnyTopic',
    'whocanmarkfavoritereplyonowntopic':
        'whoCanMarkFavoriteReplyOnOwnTopic',
    'whocanmarknoresponseneeded':
        'whoCanMarkNoResponseNeeded',
    'whocanmoderatecontent':
        'whoCanModerateContent',
    'whocanmoderatemembers':
        'whoCanModerateMembers',
    'whocanmodifymembers':
        'whoCanModifyMembers',
    'whocanmodifytagsandcategories':
        'whoCanModifyTagsAndCategories',
    'whocanmovetopicsin':
        'whoCanMoveTopicsIn',
    'whocanmovetopicsout':
        'whoCanMoveTopicsOut',
    'whocanpostannouncements':
        'whoCanPostAnnouncements',
    'whocanpostmessage':
        'whoCanPostMessage',
    'whocantaketopics':
        'whoCanTakeTopics',
    'whocanunassigntopic':
        'whoCanUnassignTopic',
    'whocanunmarkfavoritereplyonanytopic':
        'whoCanUnmarkFavoriteReplyOnAnyTopic',
    'whocanviewgroup':
        'whoCanViewGroup',
    'whocanviewmembership':
        'whoCanViewMembership',
}


def print_():
    cd = gapi_directory.build()
    i = 3
    members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
    customer = GC_Values[GC_CUSTOMER_ID]
    usedomain = usemember = usequery = None
    aliasDelimiter = ' '
    memberDelimiter = '\n'
    todrive = False
    cdfieldsList = []
    gsfieldsList = []
    fieldsTitles = {}
    titles = []
    csvRows = []
    display.add_field_title_to_csv_file('email',
                                        GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP,
                                        cdfieldsList, fieldsTitles, titles)
    roles = []
    getSettings = sortHeaders = False
    while i < len(sys.argv):
        myarg = sys.argv[i].lower()
        if myarg == 'todrive':
            todrive = True
            i += 1
        elif myarg == 'domain':
            usedomain = sys.argv[i + 1].lower()
            customer = None
            i += 2
        elif myarg == 'member':
            usemember = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
            customer = usequery = None
            i += 2
        elif myarg == 'query':
            usequery = sys.argv[i + 1]
            usemember = None
            i += 2
        elif myarg == 'maxresults':
            # deprecated argument
            i += 2
        elif myarg == 'delimiter':
            aliasDelimiter = memberDelimiter = sys.argv[i + 1]
            i += 2
        elif myarg in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
            display.add_field_title_to_csv_file(
                myarg, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList,
                fieldsTitles, titles)
            i += 1
        elif myarg == 'settings':
            getSettings = True
            i += 1
        elif myarg == 'allfields':
            getSettings = sortHeaders = True
            cdfieldsList = []
            gsfieldsList = []
            fieldsTitles = {}
            for field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
                display.add_field_title_to_csv_file(
                    field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList,
                    fieldsTitles, titles)
            i += 1
        elif myarg == 'sortheaders':
            sortHeaders = True
            i += 1
        elif myarg == 'fields':
            fieldNameList = sys.argv[i + 1]
            for field in fieldNameList.lower().replace(',', ' ').split():
                if field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
                    display.add_field_title_to_csv_file(
                        field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP,
                        cdfieldsList, fieldsTitles, titles)
                elif field in GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP:
                    display.add_field_to_csv_file(field, {
                        field:
                            [GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP[field]]
                    }, gsfieldsList, fieldsTitles, titles)
                elif field == 'collaborative':
                    for attrName in COLLABORATIVE_INBOX_ATTRIBUTES:
                        display.add_field_to_csv_file(attrName,
                                                      {attrName: [attrName]},
                                                      gsfieldsList,
                                                      fieldsTitles, titles)
                else:
                    controlflow.invalid_argument_exit(
                        field, 'gam print groups fields')
            i += 2
        elif myarg in ['members', 'memberscount']:
            roles.append(ROLE_MEMBER)
            members = True
            if myarg == 'memberscount':
                membersCountOnly = True
            i += 1
        elif myarg in ['owners', 'ownerscount']:
            roles.append(ROLE_OWNER)
            owners = True
            if myarg == 'ownerscount':
                ownersCountOnly = True
            i += 1
        elif myarg in ['managers', 'managerscount']:
            roles.append(ROLE_MANAGER)
            managers = True
            if myarg == 'managerscount':
                managersCountOnly = True
            i += 1
        else:
            controlflow.invalid_argument_exit(sys.argv[i], 'gam print groups')
    cdfields = ','.join(set(cdfieldsList))
    if gsfieldsList:
        getSettings = True
        gsfields = ','.join(set(gsfieldsList))
    elif getSettings:
        gsfields = None
    if getSettings:
        gs = gam.buildGAPIObject('groupssettings')
    roles = ','.join(sorted(set(roles)))
    if roles:
        if members:
            display.add_titles_to_csv_file([
                'MembersCount',
            ], titles)
            if not membersCountOnly:
                display.add_titles_to_csv_file([
                    'Members',
                ], titles)
        if managers:
            display.add_titles_to_csv_file([
                'ManagersCount',
            ], titles)
            if not managersCountOnly:
                display.add_titles_to_csv_file([
                    'Managers',
                ], titles)
        if owners:
            display.add_titles_to_csv_file([
                'OwnersCount',
            ], titles)
            if not ownersCountOnly:
                display.add_titles_to_csv_file([
                    'Owners',
                ], titles)
    gam.printGettingAllItems('Groups', None)
    page_message = gapi.got_total_items_first_last_msg('Groups')
    entityList = gapi.get_all_pages(cd.groups(),
                                    'list',
                                    'groups',
                                    page_message=page_message,
                                    message_attribute='email',
                                    customer=customer,
                                    domain=usedomain,
                                    userKey=usemember,
                                    query=usequery,
                                    fields=f'nextPageToken,groups({cdfields})')
    i = 0
    count = len(entityList)
    for groupEntity in entityList:
        i += 1
        groupEmail = groupEntity['email']
        group = {}
        for field in cdfieldsList:
            if field in groupEntity:
                if isinstance(groupEntity[field], list):
                    group[fieldsTitles[field]] = aliasDelimiter.join(
                        groupEntity[field])
                else:
                    group[fieldsTitles[field]] = groupEntity[field]
        if roles:
            sys.stderr.write(
                f' Getting {roles} for {groupEmail}{gam.currentCountNL(i, count)}')
            page_message = gapi.got_total_items_first_last_msg('Members')
            validRoles, listRoles, listFields = gam._getRoleVerification(
                roles, 'nextPageToken,members(email,id,role)')
            groupMembers = gapi.get_all_pages(cd.members(),
                                              'list',
                                              'members',
                                              page_message=page_message,
                                              message_attribute='email',
                                              soft_errors=True,
                                              groupKey=groupEmail,
                                              roles=listRoles,
                                              fields=listFields)
            if members:
                membersList = []
                membersCount = 0
            if managers:
                managersList = []
                managersCount = 0
            if owners:
                ownersList = []
                ownersCount = 0
            for member in groupMembers:
                member_email = member.get('email', member.get('id', None))
                if not member_email:
                    sys.stderr.write(f' Not sure what to do with: {member}')
                    continue
                role = member.get('role', ROLE_MEMBER)
                if not validRoles or role in validRoles:
                    if role == ROLE_MEMBER:
                        if members:
                            membersCount += 1
                            if not membersCountOnly:
                                membersList.append(member_email)
                    elif role == ROLE_MANAGER:
                        if managers:
                            managersCount += 1
                            if not managersCountOnly:
                                managersList.append(member_email)
                    elif role == ROLE_OWNER:
                        if owners:
                            ownersCount += 1
                            if not ownersCountOnly:
                                ownersList.append(member_email)
                    elif members:
                        membersCount += 1
                        if not membersCountOnly:
                            membersList.append(member_email)
            if members:
                group['MembersCount'] = membersCount
                if not membersCountOnly:
                    group['Members'] = memberDelimiter.join(membersList)
            if managers:
                group['ManagersCount'] = managersCount
                if not managersCountOnly:
                    group['Managers'] = memberDelimiter.join(managersList)
            if owners:
                group['OwnersCount'] = ownersCount
                if not ownersCountOnly:
                    group['Owners'] = memberDelimiter.join(ownersList)
        if getSettings and not GroupIsAbuseOrPostmaster(groupEmail):
            sys.stderr.write(
                f' Retrieving Settings for group {groupEmail}{gam.currentCountNL(i, count)}'
            )
            settings = gapi.call(gs.groups(),
                                 'get',
                                 soft_errors=True,
                                 retry_reasons=[
                                     gapi_errors.ErrorReason.SERVICE_LIMIT,
                                     gapi_errors.ErrorReason.INVALID
                                 ],
                                 groupUniqueId=mapGroupEmailForSettings(groupEmail),
                                 fields=gsfields)
            if settings:
                for key in settings:
                    if key in ['email', 'name', 'description', 'kind', 'etag']:
                        continue
                    setting_value = settings[key]
                    if setting_value is None:
                        setting_value = ''
                    if key not in titles:
                        titles.append(key)
                    group[key] = setting_value
            else:
                sys.stderr.write(
                    f' Settings unavailable for group {groupEmail}{gam.currentCountNL(i, count)}'
                )
        csvRows.append(group)
    if sortHeaders:
        display.sort_csv_titles([
            'Email',
        ], titles)
    display.write_csv_file(csvRows, titles, 'Groups', todrive)


def print_members():
    cd = gapi_directory.build()
    todrive = False
    membernames = False
    includeDerivedMembership = False
    customer = GC_Values[GC_CUSTOMER_ID]
    checkSuspended = usedomain = usemember = usequery = None
    roles = []
    fields = 'nextPageToken,members(email,id,role,status,type)'
    titles = ['group']
    csvRows = []
    groups_to_get = []
    i = 3
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'todrive':
            todrive = True
            i += 1
        elif myarg == 'domain':
            usedomain = sys.argv[i + 1].lower()
            customer = None
            i += 2
        elif myarg == 'member':
            usemember = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
            customer = usequery = None
            i += 2
        elif myarg == 'query':
            usequery = sys.argv[i + 1]
            usemember = None
            i += 2
        elif myarg == 'fields':
            memberFieldsList = sys.argv[i + 1].replace(',', ' ').lower().split()
            fields = f'nextPageToken,members({",".join(memberFieldsList)})'
            i += 2
        elif myarg == 'membernames':
            membernames = True
            titles.append('name')
            i += 1
        elif myarg in ['role', 'roles']:
            for role in sys.argv[i + 1].lower().replace(',', ' ').split():
                if role in GROUP_ROLES_MAP:
                    roles.append(GROUP_ROLES_MAP[role])
                else:
                    controlflow.system_error_exit(
                        2,
                        f'{role} is not a valid role for "gam print group-members {myarg}"'
                    )
            i += 2
        elif myarg in ['group', 'groupns', 'groupsusp']:
            group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
            groups_to_get = [{'email': group_email}]
            if myarg == 'groupns':
                checkSuspended = False
            elif myarg == 'groupsusp':
                checkSuspended = True
            i += 2
        elif myarg in ['suspended', 'notsuspended']:
            checkSuspended = myarg == 'suspended'
            i += 1
        elif myarg == 'includederivedmembership':
            includeDerivedMembership = True
            i += 1
        else:
            controlflow.invalid_argument_exit(sys.argv[i],
                                              'gam print group-members')
    if not groups_to_get:
        groups_to_get = gapi.get_all_pages(cd.groups(),
                                           'list',
                                           'groups',
                                           message_attribute='email',
                                           customer=customer,
                                           domain=usedomain,
                                           userKey=usemember,
                                           query=usequery,
                                           fields='nextPageToken,groups(email)')
    i = 0
    count = len(groups_to_get)
    for group in groups_to_get:
        i += 1
        group_email = group['email']
        sys.stderr.write(
            f'Getting members for {group_email}{gam.currentCountNL(i, count)}')
        validRoles, listRoles, listFields = gam._getRoleVerification(
            ','.join(roles), fields)
        group_members = gapi.get_all_pages(
            cd.members(),
            'list',
            'members',
            soft_errors=True,
            includeDerivedMembership=includeDerivedMembership,
            groupKey=group_email,
            roles=listRoles,
            fields=listFields)
        for member in group_members:
            if not _checkMemberRoleIsSuspended(member, validRoles,
                                               checkSuspended):
                continue
            for title in member:
                if title not in titles:
                    titles.append(title)
            member['group'] = group_email
            if membernames and 'type' in member and 'id' in member:
                if member['type'] == 'USER':
                    try:
                        mbinfo = gapi.call(
                            cd.users(),
                            'get',
                            throw_reasons=[
                                gapi_errors.ErrorReason.USER_NOT_FOUND,
                                gapi_errors.ErrorReason.NOT_FOUND,
                                gapi_errors.ErrorReason.FORBIDDEN
                            ],
                            userKey=member['id'],
                            fields='name')
                        memberName = mbinfo['name']['fullName']
                    except (gapi_errors.GapiUserNotFoundError,
                            gapi_errors.GapiNotFoundError,
                            gapi_errors.GapiForbiddenError):
                        memberName = 'Unknown'
                elif member['type'] == 'GROUP':
                    try:
                        mbinfo = gapi.call(
                            cd.groups(),
                            'get',
                            throw_reasons=[
                                gapi_errors.ErrorReason.NOT_FOUND,
                                gapi_errors.ErrorReason.FORBIDDEN
                            ],
                            groupKey=member['id'],
                            fields='name')
                        memberName = mbinfo['name']
                    except (gapi_errors.GapiNotFoundError,
                            gapi_errors.GapiForbiddenError):
                        memberName = 'Unknown'
                elif member['type'] == 'CUSTOMER':
                    try:
                        mbinfo = gapi.call(
                            cd.customers(),
                            'get',
                            throw_reasons=[
                                gapi_errors.ErrorReason.BAD_REQUEST,
                                gapi_errors.ErrorReason.RESOURCE_NOT_FOUND,
                                gapi_errors.ErrorReason.FORBIDDEN
                            ],
                            customerKey=member['id'],
                            fields='customerDomain')
                        memberName = mbinfo['customerDomain']
                    except (gapi_errors.GapiBadRequestError,
                            gapi_errors.GapiResourceNotFoundError,
                            gapi_errors.GapiForbiddenError):
                        memberName = 'Unknown'
                else:
                    memberName = 'Unknown'
                member['name'] = memberName
            csvRows.append(member)
    display.write_csv_file(csvRows, titles, 'Group Members', todrive)


def _checkMemberRoleIsSuspended(member, validRoles, isSuspended):
    if validRoles and member.get('role', ROLE_MEMBER) not in validRoles:
        return False
    if isSuspended is None:
        return True
    memberStatus = member.get('status', 'UNKNOWN')
    if not isSuspended:
        return memberStatus != 'SUSPENDED'
    return memberStatus == 'SUSPENDED'


UPDATE_GROUP_SUBCMDS = ['add', 'clear', 'delete', 'remove', 'sync', 'update']
GROUP_ROLES_MAP = {
    'owner': ROLE_OWNER,
    'owners': ROLE_OWNER,
    'manager': ROLE_MANAGER,
    'managers': ROLE_MANAGER,
    'member': ROLE_MEMBER,
    'members': ROLE_MEMBER,
}
MEMBER_DELIVERY_MAP = {
    'allmail': 'ALL_MAIL',
    'digest': 'DIGEST',
    'daily': 'DAILY',
    'abridged': 'DAILY',
    'nomail': 'NONE',
    'none': 'NONE'
}


def update():

    # Convert foo@googlemail.com to foo@gmail.com; eliminate periods in name for foo.bar@gmail.com
    def _cleanConsumerAddress(emailAddress, mapCleanToOriginal):
        atLoc = emailAddress.find('@')
        if atLoc > 0:
            if emailAddress[atLoc + 1:] in ['gmail.com', 'googlemail.com']:
                cleanEmailAddress = emailAddress[:atLoc].replace(
                    '.', '') + '@gmail.com'
                if cleanEmailAddress != emailAddress:
                    mapCleanToOriginal[cleanEmailAddress] = emailAddress
                    return cleanEmailAddress
        return emailAddress

    def _getRoleAndUsers():
        checkSuspended = None
        role = None
        delivery = None
        i = 5
        if sys.argv[i].lower() in GROUP_ROLES_MAP:
            role = GROUP_ROLES_MAP[sys.argv[i].lower()]
            i += 1
        if sys.argv[i].lower() in ['suspended', 'notsuspended']:
            checkSuspended = sys.argv[i].lower() == 'suspended'
            i += 1
        if sys.argv[i].lower().replace('_', '') in MEMBER_DELIVERY_MAP:
            delivery = MEMBER_DELIVERY_MAP[sys.argv[i].lower().replace('_', '')]
            i += 1
        if sys.argv[i].lower() in usergroup_types:
            users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(),
                                           entity=sys.argv[i + 1],
                                           checkSuspended=checkSuspended,
                                           groupUserMembersOnly=False)
        else:
            users_email = [
                gam.normalizeEmailAddressOrUID(sys.argv[i],
                                               checkForCustomerId=True)
            ]
        return (role, users_email, delivery)

    gs_get_before_update = False
    cd = gapi_directory.build()
    group = sys.argv[3]
    myarg = sys.argv[4].lower()
    items = []
    if myarg in UPDATE_GROUP_SUBCMDS:
        group = gam.normalizeEmailAddressOrUID(group)
        if myarg == 'add':
            role, users_email, delivery = _getRoleAndUsers()
            if not role:
                role = ROLE_MEMBER
            if not exists(cd, group):
                return
            if len(users_email) > 1:
                sys.stderr.write(
                    f'Group: {group}, Will add {len(users_email)} {role}s.\n')
                for user_email in users_email:
                    item = ['gam', 'update', 'group', group, 'add', role]
                    if delivery:
                        item.append(delivery)
                    item.append(user_email)
                    items.append(item)
            elif len(users_email) > 0:
                body = {
                    'role':
                        role,
                    'email' if users_email[0].find('@') != -1 else 'id':
                        users_email[0]
                }
                add_text = [f'as {role}']
                if delivery:
                    body['delivery_settings'] = delivery
                    add_text.append(f'delivery {delivery}')
                for i in range(2):
                    try:
                        gapi.call(
                            cd.members(),
                            'insert',
                            throw_reasons=[
                                gapi_errors.ErrorReason.DUPLICATE,
                                gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
                                gapi_errors.ErrorReason.RESOURCE_NOT_FOUND,
                                gapi_errors.ErrorReason.INVALID_MEMBER,
                                gapi_errors.ErrorReason.
                                CYCLIC_MEMBERSHIPS_NOT_ALLOWED
                            ],
                            groupKey=group,
                            body=body)
                        print(
                            f' Group: {group}, {users_email[0]} Added {" ".join(add_text)}'
                        )
                        break
                    except gapi_errors.GapiDuplicateError as e:
                        # check if user is a full member, not pending
                        try:
                            result = gapi.call(
                                cd.members(),
                                'get',
                                throw_reasons=[
                                    gapi_errors.ErrorReason.MEMBER_NOT_FOUND
                                ],
                                memberKey=users_email[0],
                                groupKey=group,
                                fields='role')
                            print(
                                f' Group: {group}, {users_email[0]} Add {" ".join(add_text)} Failed: Duplicate, already a {result["role"]}'
                            )
                            break  # if get succeeds, user is a full member and we throw duplicate error
                        except gapi_errors.GapiMemberNotFoundError:
                            # insert fails on duplicate and get fails on not found, user is pending
                            print(
                                f' Group: {group}, {users_email[0]} member is pending, deleting and re-adding to solve...'
                            )
                            gapi.call(cd.members(),
                                      'delete',
                                      memberKey=users_email[0],
                                      groupKey=group)
                            continue  # 2nd insert should succeed now that pending is clear
                    except (gapi_errors.GapiMemberNotFoundError,
                            gapi_errors.GapiResourceNotFoundError,
                            gapi_errors.GapiInvalidMemberError,
                            gapi_errors.GapiCyclicMembershipsNotAllowedError
                           ) as e:
                        print(
                            f' Group: {group}, {users_email[0]} Add {" ".join(add_text)} Failed: {str(e)}'
                        )
                        break
        elif myarg == 'sync':
            syncMembersSet = set()
            syncMembersMap = {}
            role, users_email, delivery = _getRoleAndUsers()
            for user_email in users_email:
                if user_email in ('*', GC_Values[GC_CUSTOMER_ID]):
                    syncMembersSet.add(GC_Values[GC_CUSTOMER_ID])
                else:
                    syncMembersSet.add(
                        _cleanConsumerAddress(user_email.lower(),
                                              syncMembersMap))
            group = exists(cd, group)
            if group:
                currentMembersSet = set()
                currentMembersMap = {}
                for current_email in gam.getUsersToModify(
                        entity_type='group',
                        entity=group,
                        member_type=role,
                        groupUserMembersOnly=False):
                    if current_email == GC_Values[GC_CUSTOMER_ID]:
                        currentMembersSet.add(current_email)
                    else:
                        currentMembersSet.add(
                            _cleanConsumerAddress(current_email.lower(),
                                                  currentMembersMap))


# Compare incoming members and current members using the cleaned addresses; we actually add/remove with the original addresses
                to_add = [
                    syncMembersMap.get(emailAddress, emailAddress)
                    for emailAddress in syncMembersSet - currentMembersSet
                ]
                to_remove = [
                    currentMembersMap.get(emailAddress, emailAddress)
                    for emailAddress in currentMembersSet - syncMembersSet
                ]
                sys.stderr.write(
                    f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n'
                )
                for user in to_remove:
                    items.append(
                        ['gam', 'update', 'group', group, 'remove', user])
                for user in to_add:
                    item = ['gam', 'update', 'group', group, 'add']
                    if role:
                        item.append(role)
                    if delivery:
                        item.append(delivery)
                    item.append(user)
                    items.append(item)
        elif myarg in ['delete', 'remove']:
            _, users_email, _ = _getRoleAndUsers()
            if not exists(cd, group):
                return
            if len(users_email) > 1:
                sys.stderr.write(
                    f'Group: {group}, Will remove {len(users_email)} emails.\n')
                for user_email in users_email:
                    items.append(
                        ['gam', 'update', 'group', group, 'remove', user_email])
            elif len(users_email) > 0:
                try:
                    gapi.call(cd.members(),
                              'delete',
                              throw_reasons=[
                                  gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
                                  gapi_errors.ErrorReason.INVALID_MEMBER
                              ],
                              groupKey=group,
                              memberKey=users_email[0])
                    print(f' Group: {group}, {users_email[0]} Removed')
                except (gapi_errors.GapiMemberNotFoundError,
                        gapi_errors.GapiInvalidMemberError) as e:
                    print(
                        f' Group: {group}, {users_email[0]} Remove Failed: {str(e)}'
                    )
        elif myarg == 'update':
            role, users_email, delivery = _getRoleAndUsers()
            group = exists(cd, group)
            if group:
                if not role and not delivery:
                    role = ROLE_MEMBER
                if len(users_email) > 1:
                    sys.stderr.write(
                        f'Group: {group}, Will update {len(users_email)} {role}s.\n'
                    )
                    for user_email in users_email:
                        item = ['gam', 'update', 'group', group, 'update']
                        if role:
                            item.append(role)
                        if delivery:
                            item.append(delivery)
                        item.append(user_email)
                        items.append(item)
                elif len(users_email) > 0:
                    body = {}
                    update_text = []
                    if role:
                        body['role'] = role
                        update_text.append(f'to {role}')
                    if delivery:
                        body['delivery_settings'] = delivery
                        update_text.append(f'delivery {delivery}')
                    try:
                        gapi.call(cd.members(),
                                  'update',
                                  throw_reasons=[
                                      gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
                                      gapi_errors.ErrorReason.INVALID_MEMBER
                                  ],
                                  groupKey=group,
                                  memberKey=users_email[0],
                                  body=body)
                        print(
                            f' Group: {group}, {users_email[0]} Updated {" ".join(update_text)}'
                        )
                    except (gapi_errors.GapiMemberNotFoundError,
                            gapi_errors.GapiInvalidMemberError) as e:
                        print(
                            f' Group: {group}, {users_email[0]} Update to {role} Failed: {str(e)}'
                        )
        else:  # clear
            checkSuspended = None
            fields = ['email', 'id']
            roles = []
            i = 5
            while i < len(sys.argv):
                myarg = sys.argv[i].lower()
                if myarg.upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]:
                    roles.append(myarg.upper())
                    i += 1
                elif myarg in ['suspended', 'notsuspended']:
                    checkSuspended = myarg == 'suspended'
                    fields.append('status')
                    i += 1
                else:
                    controlflow.invalid_argument_exit(sys.argv[i],
                                                      'gam update group clear')
            if roles:
                roles = ','.join(sorted(set(roles)))
            else:
                roles = ROLE_MEMBER
            group = gam.normalizeEmailAddressOrUID(group)
            member_type_message = f'{roles.lower()}s'
            sys.stderr.write(
                f'Getting {member_type_message} of {group} (may take some time for large groups)...\n'
            )
            page_message = gapi.got_total_items_msg(f'{member_type_message}',
                                                    '...')
            validRoles, listRoles, listFields = gam._getRoleVerification(
                roles, f'nextPageToken,members({",".join(fields)})')
            try:
                result = gapi.get_all_pages(
                    cd.members(),
                    'list',
                    'members',
                    page_message=page_message,
                    throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
                    groupKey=group,
                    roles=listRoles,
                    fields=listFields)
                if not result:
                    print('Group already has 0 members')
                    return
                users_email = [
                    member.get('email', member['id'])
                    for member in result
                    if _checkMemberRoleIsSuspended(member, validRoles,
                                                   checkSuspended)
                ]
                if len(users_email) > 1:
                    sys.stderr.write(
                        f'Group: {group}, Will remove {len(users_email)} {"" if checkSuspended is None else ["Non-suspended ", "Suspended "][checkSuspended]}{roles}s.\n'
                    )
                    for user_email in users_email:
                        items.append([
                            'gam', 'update', 'group', group, 'remove',
                            user_email
                        ])
                elif len(users_email) > 0:
                    try:
                        gapi.call(cd.members(),
                                  'delete',
                                  throw_reasons=[
                                      gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
                                      gapi_errors.ErrorReason.INVALID_MEMBER
                                  ],
                                  groupKey=group,
                                  memberKey=users_email[0])
                        print(f' Group: {group}, {users_email[0]} Removed')
                    except (gapi_errors.GapiMemberNotFoundError,
                            gapi_errors.GapiInvalidMemberError) as e:
                        print(
                            f' Group: {group}, {users_email[0]} Remove Failed: {str(e)}'
                        )
            except (gapi_errors.GapiGroupNotFoundError,
                    gapi_errors.GapiDomainNotFoundError,
                    gapi_errors.GapiInvalidError,
                    gapi_errors.GapiForbiddenError):
                gam.entityUnknownWarning('Group', group, 0, 0)
        if items:
            gam.run_batch(items)
    else:
        i = 4
        use_cd_api = False
        verifyNotInvitable = False
        gs = None
        gs_body = {}
        cd_body = {}
        while i < len(sys.argv):
            myarg = sys.argv[i].lower().replace('_', '')
            if myarg == 'email':
                use_cd_api = True
                cd_body['email'] = gam.normalizeEmailAddressOrUID(sys.argv[i +
                                                                           1])
                i += 2
            elif myarg == 'admincreated':
                use_cd_api = True
                cd_body['adminCreated'] = gam.getBoolean(sys.argv[i + 1], myarg)
                i += 2
            elif myarg == 'getbeforeupdate':
                gs_get_before_update = True
                i += 1
            elif myarg == 'verifynotinvitable':
                verifyNotInvitable = True
                i += 1
            else:
                if not gs:
                    gs = gam.buildGAPIObject('groupssettings')
                    gs_object = gs._rootDesc
                getGroupAttrValue(myarg, sys.argv[i + 1], gs_object, gs_body,
                                  'update')
                i += 2
        group = gam.normalizeEmailAddressOrUID(group)
        if use_cd_api or (
                group.find('@') == -1
        ):  # group settings API won't take uid so we make sure cd API is used so that we can grab real email.
            if (verifyNotInvitable and 'email' in cd_body and
                gapi_cloudidentity_userinvitations.get_is_invitable_user(cd_body['email'])):
                sys.stderr.write(f'Group {group} not updated, new email {cd_body["email"]} is an unmanaged account\n')
                sys.exit(51)
            group = gapi.call(cd.groups(),
                              'update',
                              groupKey=group,
                              body=cd_body,
                              fields='email')['email']
        if gs:
            if not GroupIsAbuseOrPostmaster(group):
                if gs_get_before_update:
                    current_settings = gapi.call(
                        gs.groups(),
                        'get',
                        retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
                        groupUniqueId=mapGroupEmailForSettings(group),
                        fields='*')
                    if current_settings is not None:
                        gs_body = dict(
                            list(current_settings.items()) +
                            list(gs_body.items()))
                if gs_body:
                    gapi.call(
                        gs.groups(),
                        'update',
                        retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
                        groupUniqueId=mapGroupEmailForSettings(group),
                        body=gs_body)
        print(f'updated group {group}')


GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')


def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
    if myarg == 'collaborative':
        myarg = 'enablecollaborativeinbox'
    for (attrib,
         params) in list(gs_object['schemas']['Groups']['properties'].items()):
        if attrib in ['kind', 'etag', 'email']:
            continue
        if myarg == attrib.lower().replace('_', ''):
            if params['type'] == 'integer':
                try:
                    if value[-1:].upper() == 'M':
                        value = int(value[:-1]) * 1024 * 1024
                    elif value[-1:].upper() == 'K':
                        value = int(value[:-1]) * 1024
                    elif value[-1].upper() == 'B':
                        value = int(value[:-1])
                    else:
                        value = int(value)
                except ValueError:
                    controlflow.system_error_exit(
                        2,
                        f'{myarg} must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got {value}'
                    )
            elif params['type'] == 'string':
                if attrib == 'description':
                    value = value.replace('\\n', '\n')
                elif attrib == 'primaryLanguage':
                    value = LANGUAGE_CODES_MAP.get(value.lower(), value)
                elif attrib in GROUP_SETTINGS_LIST_ATTRIBUTES:
                    value = value.upper()
                    possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(
                        params['description'])
                    if value not in possible_values:
                        controlflow.expected_argument_exit(
                            f'value for {attrib}', ', '.join(possible_values),
                            value)
                elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES:
                    value = value.lower()
                    if value in true_values:
                        value = 'true'
                    elif value in false_values:
                        value = 'false'
                    else:
                        controlflow.expected_argument_exit(
                            f'value for {attrib}', ', '.join(['true', 'false']),
                            value)
            gs_body[attrib] = value
            return
    controlflow.invalid_argument_exit(myarg, f'gam {function} group')
