{ config, lib, pkgs, ... }:
let
  cfg = config.programs.rclone;
  targetName = "rclone";
  owncloudVendor = "owncloud";
  owncloudName = "Owncloud";
  nextcloudVendor = "nextcloud";
  mkIfNotNull = value: result: lib.mkIf (value != null) result;

  mkSystemdDependencyOption =
    { default, global ? false, ... }: lib.mkOption {
      type = lib.types.attrsOf (lib.types.listOf lib.types.str);
      description = "The systemd services ${
          if global then "all" else "this"
        } sync${
          if global then "s" else ""
        } depend${
          if global then "" else "s"
        } on.";
      example = {
        secrets = [
          "sops-nix.service"
        ];
      };
      inherit default;
    };

  mkProvider = (
    { config, ... }: {
      options = {
        path = lib.mkOption {
          type = lib.types.str;
          description = "The path to mount the remote file system to.";
        };

        autoStart = lib.mkOption {
          type = lib.types.bool;
          description = "Whether to start this sync automatically.";
          default = true;
        };

        systemdDependencies = mkSystemdDependencyOption {
          default = cfg.systemdDependencies;
        };

        environment = lib.mkOption {
          type = lib.types.attrsOf lib.types.envVar;
          description = "The environment variables to pass to the service.";
          default = { };
        };

        secrets = lib.mkOption {
          type = lib.types.attrsOf (lib.types.either lib.types.path lib.types.str);
          description = "A set of environment variables to load from files.";
          default = { };
        };

        secretsScript = lib.mkOption {
          type = lib.types.lines;
          description = "A script for loading secrets before launching the sync.";
          default = "";
        };

        config = lib.mkOption {
          type = lib.types.attrs;
          description = "The rclone config to use for creating the mount.";
          visible = false;
        };
      };

      config = {
        secretsScript = lib.strings.concatLines
          (builtins.attrValues (
            builtins.mapAttrs
              (name: path: "${name}=\"$(cat ${lib.escapeShellArg path})\"")
              config.secrets));
      };
    });

  mkWebdavProvider = (
    { displayName, vendor ? null, ... }: (
      { config, ... }: {
        imports = [
          mkProvider
        ];

        options = {
          vendor = lib.mkOption {
            type = lib.types.nullOr (lib.types.enum [
              "fastmail"
              nextcloudVendor
              owncloudVendor
              "sharepoint"
              "sharepoint-ntlm"
              "rclone"
              "other"
            ]);
            description = "The vendor of the WebDAV share.";
            default = vendor;
          };

          url = lib.mkOption {
            type = lib.types.str;
            description = "The WebDAV URL of the ${displayName} server.";
            default = null;
          };

          username = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            description = "The user name for logging in to the ${displayName} server.";
            default = null;
          };

          obscuredPassword = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            description = "The password obscured using the `rclone obscure` command for logging in to the ${displayName} server.";
            default = null;
          };

          obscuredPasswordFile = lib.mkOption {
            type = lib.types.nullOr (lib.types.either lib.types.path lib.types.str);
            description = "The path to a file containing the password obscured using the `rclone obscure` command for logging in to the ${displayName} server.";
            default = null;
          };

          bearerToken = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            description = "The bearer token for logging in to the ${displayName} server.";
            default = null;
          };

          bearerTokenFile = lib.mkOption {
            type = lib.types.nullOr (lib.types.either lib.types.path lib.types.str);
            description = "The path to a file containing the bearer token for logging in to the ${displayName} server.";
            default = null;
          };
        };

        config = {
          config = lib.mkMerge [
            { url = config.url; }
            (mkIfNotNull config.vendor { inherit (config) vendor; })
            (mkIfNotNull config.username { user = config.username; })
            (mkIfNotNull config.obscuredPassword { pass = config.obscuredPassword; })
            (mkIfNotNull config.bearerToken { bearer_token = config.bearerToken; })
          ];

          secrets = (lib.optionalAttrs (config.obscuredPasswordFile != null) {
            RCLONE_WEBDAV_PASS = config.obscuredPasswordFile;
          }) // (lib.optionalAttrs (config.bearerTokenFile != null) {
             RCLONE_WEBDAV_BEARER_TOKEN = config.bearerTokenFile;
          });
        };
      }));

  mkOwncloudProvider = { displayName ? owncloudName, vendor ? owncloudVendor }: (
    { config, ... }: {
      imports = [
        (mkWebdavProvider { inherit displayName vendor; })
      ];

      options = {
        baseUrl = lib.mkOption {
          type = lib.types.str;
          description = "The base url of the ${displayName} server for automatically determining the WebDAV url.";
        };
      };

      config = {
        url = "${lib.strings.removeSuffix "/" (lib.strings.normalizePath config.baseUrl)}/remote.php/dav/files/${config.username}";
      };
    });

  syncProviders = {
    ${nextcloudVendor} = rec {
      displayName = "Nextcloud";

      module = mkOwncloudProvider {
        inherit displayName;
        vendor = nextcloudVendor;
      };
    };

    ${owncloudVendor} = {
      displayName = owncloudName;
      module = mkOwncloudProvider { };
    };
  };
in {
  options = {
    programs.rclone = {
      enable = lib.mkEnableOption "rclone";

      systemdDependencies = mkSystemdDependencyOption {
        default = {};
        global = true;
      };

      configs = (builtins.mapAttrs
        (name: provider: lib.mkOption {
          type = lib.types.attrsOf (lib.types.submodule provider.module);
          description = "The ${provider.displayName} synchronizations to set up.";
          default = { };
        })
        syncProviders);
    };
  };

  config = {
    home.packages = lib.optional cfg.enable pkgs.rclone;

    systemd.user = lib.optionalAttrs cfg.enable {
      enable = true;

      services = {
        rclone = {
          Unit = {
            Description = "rclone Starter";
            Documentation = "man:rclone(1)";
          };

          Service = {
            Type = "simple";

            ExecStartPre =
              let
                script = pkgs.writeShellScriptBin "rclone-pre" ''
                  sleep 10
                '';
              in
                (lib.getExe script);

            ExecStart =
              let
                script = pkgs.writeShellScriptBin "rclone" ''
                  systemctl --user start rclone.target
                '';
              in
                (lib.getExe script);
          };
        };
      } // (
        lib.attrsets.concatMapAttrs
        (providerName: configs:
          lib.attrsets.concatMapAttrs
          (name: sync:
            let
              serviceName = "rclone-${providerName}-sync-${name}";
            in {
              ${serviceName} = {
                Unit = {
                  Description = "rclone sync service for ${name} at using ${providerName}";
                };

                Service = {
                  Environment = lib.mapAttrsToList
                    (key: val: (lib.escapeShellArg "${key}=${val}"))
                    sync.environment;

                  ExecStart =
                    let
                      configFile = pkgs.writeText
                        "${serviceName}.conf"
                        (lib.generators.toINI { } { name = sync.config; });
                      script = pkgs.writeShellScriptBin serviceName ''
                        ${sync.secretsScript}
                        bash -c echo hello world
                      '';
                    in
                      (lib.getExe script);
                };

                Install = {
                  WantedBy = lib.optional sync.autoStart "${targetName}.target";
                  After = builtins.concatLists (builtins.attrValues sync.systemdDependencies);
                };
              };
            })
            configs)
        cfg.configs);

      targets.${targetName} = {
        Unit = {
          Description = "rclone Mounts";
          Documentation = "man:rclone(1)";
        };
      };
    };
  };
}