{ config, lib, pkgs, ... }:

with lib;
let
  systemUserOpts = { username, ... }: {
    options = with types; {
      username = mkOption {
        type = str;
        description = "The system user's login name.";
        default = username;
      };

      description = mkOption {
        type = str;
        description = "Description of this system user's purpose or role";
      };

      ldap-hashed-password = mkOption {
        type = str;
        description =
          "LDAP-formatted hashed password for this user. Generate with slappasswd.";
      };
    };
  };

  userOpts = { username, ... }: {
    options = with types; {
      username = mkOption {
        type = str;
        description = "The user's login name.";
        default = username;
      };

      uid = mkOption {
        type = int;
        description = "Unique UID number for the user.";
      };

      common-name = mkOption {
        type = str;
        description = "The user's common or given name.";
      };

      primary-group = mkOption {
        type = str;
        description = "Primary group to which the user belongs.";
      };

      login-shell = mkOption {
        type = nullOr shellPackage;
        description = "The user's preferred shell.";
      };

      description = mkOption {
        type = str;
        default = "Fudo Member";
        description = "A description of this user's role.";
      };

      ldap-hashed-passwd = mkOption {
        type = nullOr str;
        description =
          "LDAP-formatted hashed password, used for email and other services. Use slappasswd to generate the properly-formatted password.";
        default = null;
      };

      login-hashed-passwd = mkOption {
        type = nullOr str;
        description =
          "Hashed password for shell, used for shell access to hosts. Use mkpasswd to generate the properly-formatted password.";
        default = null;
      };

      ssh-authorized-keys = mkOption {
        type = listOf str;
        description = "SSH public keys this user can use to log in.";
        default = [ ];
      };

      home-manager-config = mkOption {
        type = nullOr attrs;
        description = "Home Manager configuration for the given user.";
        default = null;
      };

      home-directory = mkOption {
        type = nullOr str;
        description = "Default home directory for the given user.";
        default = null;
      };

      k5login = mkOption {
        type = listOf str;
        description = "List of Kerberos principals that map to this user.";
        default = [ ];
      };
    };
  };

  groupOpts = { group-name, ... }: {
    options = with types; {
      group-name = mkOption {
        type = nullOr str;
        default = group-name;
        description = "Group name.";
      };

      description = mkOption {
        type = str;
        description = "Description of the group or it's purpose.";
      };

      members = mkOption {
        type = listOf str;
        default = [ ];
        description = "A list of users who are members of the current group.";
      };

      gid = mkOption {
        type = int;
        description = "GID number of the group.";
      };
    };
  };

  list-includes = list: el: isNull (findFirst (this: this == el) null list);

  filterExistingUsers = users: group-members:
    let user-list = attrNames users;
    in filter (username: list-includes user-list username) group-members;

in {
  options.fudo = {
    users = mkOption {
      type = with types; attrsOf (submodule userOpts);
      description = "Users";
      default = { };
    };

    groups = mkOption {
      type = with types; attrsOf (submodule groupOpts);
      description = "Groups";
      default = { };
    };

    system-users = mkOption {
      type = with types; attrsOf (submodule systemUserOpts);
      description = "System users (probably not what you're looking for!)";
      default = { };
    };
  };

  imports = [
    ./users-common.nix
  ];

  config = let
    sys = import ../system.nix { inherit lib config; };
    
  in {
    fudo.auth.ldap-server = let
      ldapUsers = (filterAttrs
        (username: userOpts: userOpts.ldap-hashed-password != null))
        config.fudo.users;

    in {
      users = mapAttrs (username: userOpts: {
        uid = userOpts.uid;
        group = userOpts.primary-group;
        common-name = userOpts.common-name;
        hashed-password = userOpts.ldap-hashed-password;
      }) ldapUsers;

      groups = mapAttrs (groupname: groupOpts: {
        gid = groupOpts.gid-number;
        description = groupOpts.description;
        members = filterExistingUsers ldapUsers groupOpts.members;
      }) config.fudo.groups;

      system-users = mapAttrs (username: userOpts: {
        description = userOpts.description;
        hashed-password = userOpts.ldap-hashed-passwd;
      }) config.fudo.system-users;
    };

    users = {
      users = mapAttrs (username: userOpts: {
        isNormalUser = true;
        uid = userOpts.uid;
        createHome = true;
        description = userOpts.common-name;
        group = userOpts.primary-group;
        home = if (userOpts.home-directory != null) then
          userOpts.home-directory
        else
          "/home/${userOpts.primary-group}/${username}";
        hashedPassword = userOpts.login-hashed-passwd;
        openssh.authorizedKeys.keys = userOpts.ssh-authorized-keys;
      }) sys.local-users;

      groups = (mapAttrs (groupname: groupOpts: {
        gid = groupOpts.gid;
        members = filterExistingUsers sys.local-users groupOpts.members;
      }) sys.local-groups) // {
        wheel = { members = sys.local-admins; };
      };
    };

    home-manager = {
      useGlobalPkgs = true;

      users = let
        home-manager-users =
          filterAttrs (username: userOpts: userOpts.home-manager-config != null)
            sys.local-users;
      in mapAttrs (username: userOpts: userOpts.home-manager-config) home-manager-users;
      
      # users = let
      #   home-manager-users =
      #     filterAttrs (username: userOpts: userOpts.home-manager-config != null)
      #       local-users;
      #   common-user-config = username: {
      #     home.file.".k5login" = {
      #       source = pkgs.writeText "${username}-k5login" ''
      #       ${concatStringsSep "\n" config.fudo.users.${username}.k5login}
      #     '';
      #     };
      #   };
      # in mapAttrs (username: userOpts:
      #   userOpts.home-manager-config // (common-user-config username))
      #   home-manager-users;
    };
  };
}