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

let
  cfg = config.services.mpdscribble;
  mpdCfg = config.services.mpd;
  mpdOpt = options.services.mpd;

  endpointUrls = {
    "last.fm" = "http://post.audioscrobbler.com";
    "libre.fm" = "http://turtle.libre.fm";
    "jamendo" = "http://postaudioscrobbler.jamendo.com";
    "listenbrainz" = "http://proxy.listenbrainz.org";
  };
in
{
  meta.maintainers = [ lib.hm.maintainers.msyds ];

  options.services.mpdscribble = {

    enable = lib.mkEnableOption ''
      mpdscribble, an MPD client which submits info about tracks being played to
      Last.fm (formerly AudioScrobbler)
    '';

    proxy = lib.mkOption {
      default = null;
      type = lib.types.nullOr lib.types.str;
      description = ''
        HTTP proxy URL.
      '';
    };

    verbose = lib.mkOption {
      default = 1;
      type = lib.types.int;
      description = ''
        Log level for the mpdscribble daemon.
      '';
    };

    journalInterval = lib.mkOption {
      default = 600;
      example = 60;
      type = lib.types.int;
      description = ''
        How often should mpdscribble save the journal file? [seconds]
      '';
    };

    host = lib.mkOption {
      default = (
        if mpdCfg.network.listenAddress != "any" then mpdCfg.network.listenAddress else "localhost"
      );
      defaultText = lib.literalExpression ''
        if config.${mpdOpt.network.listenAddress} != "any"
        then config.${mpdOpt.network.listenAddress}
        else "localhost"
      '';
      type = lib.types.str;
      description = ''
        Host for the mpdscribble daemon to search for a mpd daemon on.
      '';
    };

    package = lib.mkPackageOption pkgs "mpdscribble" { };

    passwordFile = lib.mkOption {
      default = null;
      type = lib.types.nullOr lib.types.str;
      description = ''
        File containing the password for the mpd daemon.
      '';
    };

    port = lib.mkOption {
      default = mpdCfg.network.port;
      defaultText = lib.literalExpression "config.${mpdOpt.network.port}";
      type = lib.types.port;
      description = ''
        Port for the mpdscribble daemon to search for a mpd daemon on.
      '';
    };

    endpoints = lib.mkOption {
      type =
        let
          endpoint =
            { name, ... }:
            {
              options = {
                url = lib.mkOption {
                  type = lib.types.str;
                  default = endpointUrls.${name} or "";
                  description = "The url endpoint where the scrobble API is listening.";
                };
                username = lib.mkOption {
                  type = lib.types.str;
                  description = ''
                    Username for the scrobble service.
                  '';
                };
                passwordFile = lib.mkOption {
                  type = lib.types.nullOr lib.types.str;
                  description = "File containing the password, either as MD5SUM or cleartext.";
                };
              };
            };
        in
        lib.types.attrsOf (lib.types.submodule endpoint);
      default = { };
      example = {
        "last.fm" = {
          username = "foo";
          passwordFile = "/run/secrets/lastfm_password";
        };
      };
      description = ''
        Endpoints to scrobble to.
        If the endpoint is one of "${lib.concatStringsSep ''", "'' (builtins.attrNames endpointUrls)}" the url is set automatically.
      '';
    };

  };

  config = lib.mkIf cfg.enable {
    assertions = [
      (lib.hm.assertions.assertPlatform "services.mpdscribble" pkgs lib.platforms.linux)
    ];
    systemd.user.services.mpdscribble =
      let
        localMpd = (cfg.host == "localhost" || cfg.host == "127.0.0.1");

        mkSection = secname: secCfg: ''
          [${secname}]
          url      = ${secCfg.url}
          username = ${secCfg.username}
          password = {{${secname}_PASSWORD}}
          journal  = /var/lib/mpdscribble/${secname}.journal
        '';

        endpoints = lib.concatStringsSep "\n" (lib.mapAttrsToList mkSection cfg.endpoints);
        cfgTemplate = pkgs.writeText "mpdscribble.conf" ''
          ## This file was automatically genenrated by home-manager and will be
          ## overwritten.  Do not edit. Edit your home-manager configuration instead.

          ## mpdscribble - an audioscrobbler for the Music Player Daemon.
          ## http://mpd.wikia.com/wiki/Client:mpdscribble

          # HTTP proxy URL.
          ${lib.optionalString (cfg.proxy != null) "proxy = ${cfg.proxy}"}

          # The location of the mpdscribble log file.  The special value
          # "syslog" makes mpdscribble use the local syslog daemon.  On most
          # systems, log messages will appear in /var/log/daemon.log then.
          # "-" means log to stderr (the current terminal).
          log = -

          # How verbose mpdscribble's logging should be.  Default is 1.
          verbose = ${toString cfg.verbose}

          # How often should mpdscribble save the journal file? [seconds]
          journal_interval = ${toString cfg.journalInterval}

          # The host running MPD, possibly protected by a password
          # ([PASSWORD@]HOSTNAME).
          host = ${(lib.optionalString (cfg.passwordFile != null) "{{MPD_PASSWORD}}@") + cfg.host}

          # The port that the MPD listens on and mpdscribble should try to
          # connect to.
          port = ${toString cfg.port}

          ${endpoints}
        '';

        configFile = "\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/mpdscribble/mpdscribble.conf";

        replaceSecret =
          secretFile: placeholder: targetFile:
          lib.optionalString (secretFile != null) ''
            ${pkgs.replace-secret}/bin/replace-secret '${placeholder}' '${secretFile}' "${targetFile}"
          '';

        preStart = pkgs.writeShellApplication {
          name = "mpdscribble-pre-start";
          runtimeInputs = [
            pkgs.replace-secret
            pkgs.coreutils
          ];
          text = ''
            configFile="${configFile}"
            mkdir -p "$(dirname "$configFile")"
            cp --no-preserve=mode,ownership -f "${cfgTemplate}" "$configFile"
            ${replaceSecret cfg.passwordFile "{{MPD_PASSWORD}}" "$configFile"}
            ${lib.concatStringsSep "\n" (
              lib.mapAttrsToList (
                secname: cfg: replaceSecret cfg.passwordFile "{{${secname}_PASSWORD}}" "$configFile"
              ) cfg.endpoints
            )}
          '';
        };

        start = pkgs.writeShellScript "mpdscribble-start" ''
          configFile="${configFile}"
          exec "${lib.getExe cfg.package}" --no-daemon --conf "$configFile"
        '';

      in
      {
        Unit = {
          Description = "mpdscribble mpd scrobble client";
          After = [ "network.target" ] ++ lib.optional localMpd "mpd.service";
        };
        Install.WantedBy = [ "default.target" ];
        Service = {
          StateDirectory = "mpdscribble";
          RuntimeDirectory = "mpdscribble";
          RuntimeDirectoryMode = "700";
          # TODO use LoadCredential= instead of running preStart with full privileges?
          ExecStartPre = "+${preStart}/bin/mpdscribble-pre-start";
          ExecStart = "${start}";
        };
      };
  };
}
