import getopt
import sys
import traceback
from collections.abc import Callable

import win32api
import win32net
import win32netcon
import win32security

verbose_level = 0

server = None  # Run on local machine.


def verbose(msg):
    if verbose_level:
        print(msg)


def CreateUser():
    "Creates a new test user, then deletes the user"
    testName = "PyNetTestUser"
    try:
        win32net.NetUserDel(server, testName)
        print("Warning - deleted user before creating it!")
    except win32net.error:
        pass

    d = {}
    d["name"] = testName
    d["password"] = "deleteme"
    d["priv"] = win32netcon.USER_PRIV_USER
    d["comment"] = "Delete me - created by Python test code"
    d["flags"] = win32netcon.UF_NORMAL_ACCOUNT | win32netcon.UF_SCRIPT
    win32net.NetUserAdd(server, 1, d)
    try:
        try:
            win32net.NetUserChangePassword(server, testName, "wrong", "new")
            print("ERROR: NetUserChangePassword worked with a wrong password!")
        except win32net.error:
            pass
        win32net.NetUserChangePassword(server, testName, "deleteme", "new")
    finally:
        win32net.NetUserDel(server, testName)
    print("Created a user, changed their password, and deleted them!")


def UserEnum():
    "Enumerates all the local users"
    resume = 0
    nuser = 0
    while 1:
        data, total, resume = win32net.NetUserEnum(
            server, 3, win32netcon.FILTER_NORMAL_ACCOUNT, resume
        )
        verbose(
            "Call to NetUserEnum obtained %d entries of %d total" % (len(data), total)
        )
        for user in data:
            verbose("Found user %s" % user["name"])
            nuser += 1
        if not resume:
            break
    assert nuser, "Could not find any users!"
    print("Enumerated all the local users")


def GroupEnum():
    "Enumerates all the domain groups"
    nmembers = 0
    resume = 0
    while 1:
        data, total, resume = win32net.NetGroupEnum(server, 1, resume)
        # print(f"Call to NetGroupEnum obtained {len(data)} entries of {total} total")
        for group in data:
            verbose("Found group {name}:{comment} ".format(**group))
            memberresume = 0
            while 1:
                memberdata, total, memberresume = win32net.NetGroupGetUsers(
                    server, group["name"], 0, resume
                )
                for member in memberdata:
                    verbose(" Member {name}".format(**member))
                    nmembers += 1
                if memberresume == 0:
                    break
        if not resume:
            break
    assert nmembers, "Couldn't find a single member in a single group!"
    print("Enumerated all the groups")


def LocalGroupEnum():
    "Enumerates all the local groups"
    resume = 0
    nmembers = 0
    while 1:
        data, total, resume = win32net.NetLocalGroupEnum(server, 1, resume)
        for group in data:
            verbose("Found group {name}:{comment} ".format(**group))
            memberresume = 0
            while 1:
                memberdata, total, memberresume = win32net.NetLocalGroupGetMembers(
                    server, group["name"], 2, resume
                )
                for member in memberdata:
                    # Just for the sake of it, we convert the SID to a username
                    username, domain, type = win32security.LookupAccountSid(
                        server, member["sid"]
                    )
                    nmembers += 1
                    verbose(" Member {} ({})".format(username, member["domainandname"]))
                if memberresume == 0:
                    break
        if not resume:
            break
    assert nmembers, "Couldn't find a single member in a single group!"
    print("Enumerated all the local groups")


def ServerEnum():
    "Enumerates all servers on the network"
    resume = 0
    while 1:
        data, total, resume = win32net.NetServerEnum(
            server, 100, win32netcon.SV_TYPE_ALL, None, resume
        )
        for s in data:
            verbose("Found server %s" % s["name"])
            # Now loop over the shares.
            shareresume = 0
            while 1:
                sharedata, total, shareresume = win32net.NetShareEnum(
                    server, 2, shareresume
                )
                for share in sharedata:
                    verbose(
                        " %(netname)s (%(path)s):%(remark)s - in use by %(current_uses)d users"
                        % share
                    )
                if not shareresume:
                    break
        if not resume:
            break
    print("Enumerated all the servers on the network")


def LocalGroup(uname=None):
    "Creates a local group, adds some members, deletes them, then removes the group"
    level = 3
    if uname is None:
        uname = win32api.GetUserName()
    if uname.find("\\") < 0:
        uname = win32api.GetDomainName() + "\\" + uname
    group = "python_test_group"
    # delete the group if it already exists
    try:
        win32net.NetLocalGroupDel(server, group)
        print("WARNING: existing local group '%s' has been deleted.")
    except win32net.error:
        pass
    group_data = {"name": group}
    win32net.NetLocalGroupAdd(server, 1, group_data)
    try:
        u = {"domainandname": uname}
        win32net.NetLocalGroupAddMembers(server, group, level, [u])
        mem, tot, res = win32net.NetLocalGroupGetMembers(server, group, level)
        print("members are", mem)
        if mem[0]["domainandname"] != uname:
            print(f"ERROR: LocalGroup just added {uname}, but members are {mem!r}")
        # Convert the list of dicts to a list of strings.
        win32net.NetLocalGroupDelMembers(
            server, group, [m["domainandname"] for m in mem]
        )
    finally:
        win32net.NetLocalGroupDel(server, group)
    print("Created a local group, added and removed members, then deleted the group")


def GetInfo(userName=None):
    "Dumps level 3 information about the current user"
    if userName is None:
        userName = win32api.GetUserName()
    print("Dumping level 3 information about user")
    info = win32net.NetUserGetInfo(server, userName, 3)
    for key, val in info.items():
        verbose(f"{key}={val}")


def SetInfo(userName=None):
    "Attempts to change the current users comment, then set it back"
    if userName is None:
        userName = win32api.GetUserName()
    oldData = win32net.NetUserGetInfo(server, userName, 3)
    try:
        d = oldData | {"usr_comment": "Test comment"}
        win32net.NetUserSetInfo(server, userName, 3, d)
        new = win32net.NetUserGetInfo(server, userName, 3)["usr_comment"]
        if str(new) != "Test comment":
            raise RuntimeError("Could not read the same comment back - got %s" % new)
        print("Changed the data for the user")
    finally:
        win32net.NetUserSetInfo(server, userName, 3, oldData)


def SetComputerInfo():
    "Doesn't actually change anything, just make sure we could ;-)"
    info = win32net.NetWkstaGetInfo(None, 502)
    # *sob* - but we can't!  Why not!!!
    # win32net.NetWkstaSetInfo(None, 502, info)


def usage(tests):
    import os

    print("Usage: %s [-s server ] [-v] [Test ...]" % os.path.basename(sys.argv[0]))
    print("  -v : Verbose - print more information")
    print("  -s : server - execute the tests against the named server")
    print("  -c : include the CreateUser test by default")
    print("where Test is one of:")
    for t in tests:
        print(t.__name__, ":", t.__doc__)
    print()
    print("If not tests are specified, all tests are run")
    sys.exit(1)


def main():
    tests = [ob for ob in globals().values() if isinstance(ob, Callable) and ob.__doc__]
    opts, args = getopt.getopt(sys.argv[1:], "s:hvc")
    create_user = False
    for opt, val in opts:
        if opt == "-s":
            global server
            server = val
        if opt == "-h":
            usage(tests)
        if opt == "-v":
            global verbose_level
            verbose_level += 1
        if opt == "-c":
            create_user = True

    if len(args) == 0:
        print("Running all tests - use '-h' to see command-line options...")
        dotests = tests
        if not create_user:
            dotests.remove(CreateUser)
    else:
        dotests = []
        for arg in args:
            for t in tests:
                if t.__name__ == arg:
                    dotests.append(t)
                    break
            else:
                print("Test '%s' unknown - skipping" % arg)
    if not dotests:
        print("Nothing to do!")
        usage(tests)
    for test in dotests:
        try:
            test()
        except:
            print("Test %s failed" % test.__name__)
            traceback.print_exc()


if __name__ == "__main__":
    main()
