{ pkgs
, lib
, config
, ... }: with lib; let
  cfg = config.services.tubesync;
in {
  options.services.tubesync = {
    enable = mkEnableOption "tubesync stack";
    debug = mkEnableOption "debug logging";
    package = mkOption {
      type = with types; package;
      default = pkgs.tubesync;
      description = "tubesync launcher package";
    };

    workers = mkOption {
      type = with types; int;
      default = 1;
      description = "maximum amount of concurrent workers";
    };

    user = mkOption {
      type = with types; str;
      default = "tubesync";
      description = "user under which tubesync runs";
    };
    group = mkOption {
      type = with types; str;
      default = "tubesync";
      description = "group under which tubesync runs";
    };

    listen = {
      host = mkOption {
        type = with types; str;
        default = "127.0.0.1";
        description = "host to listen on";
      };
      port = mkOption {
        type = with types; port;
        default = 8080;
        description = "port to listen on";
      };
    };

    stateDir = mkOption {
      type = with types; str;
      default = "/var/lib/tubesync";
      description = "path to tubesync state storage directory";
    };

    dataDir = mkOption {
      type = with types; str;
      default = "${cfg.stateDir}/downloads";
      description = "path to tubesync video downloads";
    };

    database = mkOption {
      type = with types; str;
      default = "postgresql://tubesync:@localhost:5432/tubesync";
      description = "database connection string";
    };
  };

  config = mkIf cfg.enable {
    systemd.services = let
      env = {
        GUNICORN_PID_FILE = "${cfg.stateDir}/run/gunicorn.pid";
        GUNICORN_USER = cfg.user;
        GUNICORN_GROUP = cfg.group;
        DATABASE_CONNECTION = cfg.database;
        CONFIG_BASE_DIR = cfg.stateDir;
        DOWNLOADS_BASE_DIR = cfg.dataDir;
        TUBESYNC_DEBUG = mkIf cfg.debug "True";
        TUBESYNC_WORKERS = toString cfg.workers;

        PYTHONPATH = cfg.package.pythonPath;
        REDIS_CONNECTION = "redis+socket://"
        + "${cfg.stateDir}/run/redis.sock";
      };

      base = description: {
        description = "tubesync: ${description}";
        wantedBy = [ "multi-user.target" ];
        environment = env;
        path = [ cfg.package ];
        serviceConfig = {
          WorkingDirectory = cfg.stateDir;
          User = cfg.user;
          Group = cfg.group;
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          NoNewPrivileges = true;
          PrivateTmp = true;
          PrivateDevices = true;
          PrivateUsers = false;
          ProtectClock = true;
          ProtectControlGroups = true;
          ProtectHome = true;
          ProtectHostname = true;
          ProtectKernelLogs = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;
          ProtectProc = "invisible";
          ProcSubset = "all";
          ProtectSystem = "strict";
          RemoveIPC = true;
          ReadWritePaths = with cfg; [ stateDir dataDir ];
          RestrictAddressFamilies = [
            "AF_INET"
            "AF_INET6"
            "AF_NETLINK"
            "AF_UNIX"
          ];
          SystemCallArchitectures = "native";
          SystemCallFilter = [
            "@system-service"
            "~@privileged"
            "@chown"
          ];
          #UMask = "0077";
        };
      };

      base' = description: (base description) // {
        after = [ "tubesync.service" ];
        partOf = [ "tubesync.service" ];
      };
    in {
      tubesync = recursiveUpdate (base "gunicorn") ({
        after = [ "network.target" ];
        serviceConfig = {
          PIDFile = env.GUNICORN_PID_FILE;
          ExecStartPre = pkgs.writeShellScript "tubesync-setup" ''
            set -xe
            tubesync-migrate
            mkdir -p "${cfg.stateDir}/run"

            mkdir -p "/tmp/tubesync"
            cp -r "${cfg.package.app}/static/." "/tmp/tubesync/static"
            chmod +w -R "/tmp/tubesync/static"
          '';
          ExecStart = "${cfg.package}/bin/tubesync-gunicorn";
          ExecReload = "/usr/bin/env kill -s HUP $MAINPID";
          ExecStop = "/usr/bin/env kill -s TERM $MAINPID";
          ExecStopPost = pkgs.writeShellScript "tubesync-cleanup" ''
            rm -f "$GUNICORN_PID_FILE"
            rm -rf "/tmp/tubesync"
          '';
        };
      });

      tubesync-worker = recursiveUpdate (base' "worker") ({
        serviceConfig.ExecStart = "${cfg.package}/bin/tubesync-worker";
      });

      # allow binding to unix socket
      redis-tubesync-celery.serviceConfig.ReadWritePaths = [ "${cfg.stateDir}/run" ];
    };

    services.redis.servers.tubesync-celery = {
      enable = true;
      inherit (cfg) user;
      unixSocket = "${cfg.stateDir}/run/redis.sock";
      save = [ ];
    };

    users.users = mkIf (cfg.user == "tubesync") {
      tubesync = {
        description = "tubesync service account";
        group = cfg.group;
        uid = config.ids.uids.tubesync;
      };
    };

    users.groups = mkIf (cfg.group == "tubesync") {
      tubesync.gid = config.ids.gids.tubesync;
    };

    ids.uids.tubesync = 101;
    ids.gids.tubesync = 101;
  };
}
