#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# SPDX-FileCopyrightText: 2007-2026 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

"""
This script tries to determine the best fileserver for user shares and
class shares for each organizational unit and writes the determined
servers to the OU object.
"""

from __future__ import print_function

import argparse
import sys

from ldap.filter import filter_format

import univention.admin.config
import univention.admin.modules
import univention.admin.objects
import univention.admin.uldap
import univention.config_registry

ucr = univention.config_registry.ConfigRegistry()
ucr.load()


def findComputerNameWithService(computer_module, lo, service, ouDn):
    computers = univention.admin.modules.lookup(
        computer_module,
        None,
        lo,
        scope="sub",
        base=ouDn,
        filter=filter_format("univentionService=%s", [service]),
    )
    for computer in computers:
        return computer.dn
    return None


def main():
    description = """%(prog)s updates the (specified) OU objects and sets the file servers for
class shares and home shares. These servers are read from the import scripts
during creation of new classes or users.
"""

    parser = argparse.ArgumentParser(description=description)
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="do not change any object but print changes instead to stdout",
    )
    parser.add_argument(
        "--auto-detect",
        action="store_true",
        help="this option tries to detect the file servers automatically",
    )
    parser.add_argument("--ou", help="name of ou that shall be updated/modified")
    options = parser.parse_args()

    if not options.auto_detect:
        parser.error("This tool only supports --auto-detect at the moment.")

    if options.dry_run:
        print("DRY-RUN: no change will be written to LDAP")

    baseDN = ucr["ldap/base"]

    lo, position = univention.admin.uldap.getAdminConnection()

    univention.admin.modules.update()
    computer_module = univention.admin.modules.get("computers/computer")
    dcmaster_module = univention.admin.modules.get("computers/domaincontroller_master")
    ou_module = univention.admin.modules.get("container/ou")
    cn_module = univention.admin.modules.get("container/cn")
    for module in (computer_module, dcmaster_module, ou_module, cn_module):
        univention.admin.modules.init(lo, position, module)

    is_singlemaster = ucr.is_true("ucsschool/singlemaster", False)
    master_dn = None
    host_list = univention.admin.modules.lookup(dcmaster_module, None, lo, scope="sub", base=baseDN)
    for hostobj in host_list:
        master_dn = hostobj.dn
    if not master_dn:
        parser.error("Cannot determine DN of Primary Directory Node - stopping here")

    # get list of OU UDM objects
    ou_list = []
    ou_filter = None
    if options.ou:
        # user has specified a specific OU on command line - add appropriate LDAP filter
        ou_filter = filter_format("ou=%s", [options.ou])
    ou_obj_list = univention.admin.modules.lookup(
        ou_module, None, lo, scope="sub", base=baseDN, filter=ou_filter
    )
    for ou_obj in ou_obj_list:
        # FIXME: lame check: if OU contains a computer container then it is a UCS@school OU
        if univention.admin.modules.lookup(
            cn_module, None, lo, scope="one", base=ou_obj.dn, filter="cn=computers"
        ):
            # only remember OU DNs if it contains a cn=computers subcontainer
            ou_list.append(ou_obj.dn)

    # iterate over all found OUs
    for ou_dn in ou_list:
        modlist = []
        homeshareserver = None
        classshareserver = None

        ou_attrs = lo.get(ou_dn)
        if b"ucsschoolOrganizationalUnit" not in ou_attrs["objectClass"]:
            modlist.append(("objectClass", b"", b"ucsschoolOrganizationalUnit"))

        if is_singlemaster:
            # in single server environments the Primary is always the correct fileserver for shares
            homeshareserver = master_dn
            classshareserver = master_dn
        else:
            # otherwise try to find a suitable server object in OU with one of the following services -
            # the first match wins
            for servicename in (
                "Windows Home Server",
                "Samba 4",
                "Samba 3",
            ):
                serverDn = findComputerNameWithService(computer_module, lo, servicename, ou_dn)
                if serverDn is not None:
                    if options.dry_run:
                        print("Found server providing service %s: %s" % (servicename, serverDn))
                    homeshareserver = serverDn
                    classshareserver = serverDn
                    break

        if not classshareserver:
            print("OU: %s" % ou_dn)
            print("ERROR: cannot determine fileserver for class shares", file=sys.stderr)
            print("ERROR: cannot determine fileserver for user home shares", file=sys.stderr)
            continue

        print("OU: %s" % ou_dn)
        if not ou_attrs.get("ucsschoolClassShareFileServer", [b""])[0]:
            modlist.append(
                (
                    "ucsschoolClassShareFileServer",
                    ou_attrs.get("ucsschoolClassShareFileServer", [b""])[0],
                    classshareserver.encode("UTF-8"),
                )
            )
            print("    ==> ucsschoolClassShareFileServer = %s" % classshareserver)
        else:
            print("    ==> ucsschoolClassShareFileServer has already been set")

        if not ou_attrs.get("ucsschoolHomeShareFileServer", [b""])[0]:
            modlist.append(
                (
                    "ucsschoolHomeShareFileServer",
                    ou_attrs.get("ucsschoolHomeShareFileServer", [b""])[0],
                    homeshareserver.encode("UTF-8"),
                )
            )
            print("    ==> ucsschoolHomeShareFileServer = %s" % homeshareserver)
        else:
            print("    ==> ucsschoolHomeShareFileServer has already been set")

        if modlist:
            if options.dry_run:
                print("DRY-RUN: skipping LDAP modification")
            else:
                try:
                    lo.modify(ou_dn, modlist)
                except Exception:
                    import traceback

                    print("ERROR: changing object %s failed!" % (ou_dn,))
                    print("ERROR: modlist = %r" % (modlist,))
                    print("ERROR: exception = %s" % (traceback.format_exc(),))


if __name__ == "__main__":
    main()
