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

let
  cfg = config.services.weblate;

  dataDir = "/var/lib/weblate";
  cacheDir = "${dataDir}/cache";
  settingsDir = "${dataDir}/settings";

  finalPackage = cfg.package.overridePythonAttrs (old: {
    # We only support the PostgreSQL backend in this module
    dependencies = old.dependencies ++ cfg.package.optional-dependencies.postgres;
    # Use a settings module in dataDir, to avoid having to rebuild the package
    # when user changes settings.
    makeWrapperArgs = (old.makeWrapperArgs or [ ]) ++ [
      "--set PYTHONPATH  \"${settingsDir}\""
      "--set DJANGO_SETTINGS_MODULE \"settings\""
    ];
  });
  inherit (finalPackage) python;

  pythonEnv = python.buildEnv.override {
    extraLibs = with python.pkgs; [
      (toPythonModule finalPackage)
      celery
    ];
  };

  # This extends and overrides the weblate/settings_example.py code found in upstream.
  weblateConfig = ''
    # This was autogenerated by the NixOS module.

    SITE_TITLE = "Weblate"
    SITE_DOMAIN = "${cfg.localDomain}"
    # TLS terminates at the reverse proxy, but this setting controls how links to weblate are generated.
    ENABLE_HTTPS = True
    SESSION_COOKIE_SECURE = ENABLE_HTTPS
    DATA_DIR = "${dataDir}"
    CACHE_DIR = f"{DATA_DIR}/cache"
    STATIC_ROOT = "${finalPackage.static}"
    MEDIA_ROOT = "/var/lib/weblate/media"
    COMPRESS_ROOT = "${finalPackage.static}"
    COMPRESS_OFFLINE = True
    DEBUG = False

    with open("${cfg.djangoSecretKeyFile}") as f:
      SECRET_KEY = f.read().rstrip("\n")

    CACHES = {
      "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "unix://${config.services.redis.servers.weblate.unixSocket}",
        "OPTIONS": {
          "CLIENT_CLASS": "django_redis.client.DefaultClient",
          "PASSWORD": None,
          "CONNECTION_POOL_KWARGS": {},
        },
        "KEY_PREFIX": "weblate",
        "TIMEOUT": 3600,
      },
      "avatar": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "/var/lib/weblate/avatar-cache",
        "TIMEOUT": 86400,
        "OPTIONS": {"MAX_ENTRIES": 1000},
      }
    }

    CELERY_TASK_ALWAYS_EAGER = False
    CELERY_BROKER_URL = "redis+socket://${config.services.redis.servers.weblate.unixSocket}"
    CELERY_RESULT_BACKEND = CELERY_BROKER_URL

    VCS_BACKENDS = ("weblate.vcs.git.GitRepository",)

    SITE_URL = "https://{}".format(SITE_DOMAIN)

    # WebAuthn
    OTP_WEBAUTHN_RP_NAME = SITE_TITLE
    OTP_WEBAUTHN_RP_ID = SITE_DOMAIN.split(":")[0]
    OTP_WEBAUTHN_ALLOWED_ORIGINS = [SITE_URL]
  ''
  + lib.optionalString cfg.configurePostgresql ''
    DATABASES = {
      "default": {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": "/run/postgresql",
        "NAME": "weblate",
        "USER": "weblate",
      }
    }
  ''
  + lib.optionalString cfg.smtp.enable ''
    EMAIL_HOST = "${cfg.smtp.host}"
    EMAIL_USE_TLS = True
    EMAIL_PORT = ${toString cfg.smtp.port}
    SERVER_EMAIL = "${cfg.smtp.from}"
    DEFAULT_FROM_EMAIL = "${cfg.smtp.from}"
  ''
  + lib.optionalString (cfg.smtp.enable && cfg.smtp.user != null) ''
    ADMINS = (("Weblate Admin", "${cfg.smtp.user}"),)
    EMAIL_HOST_USER = "${cfg.smtp.user}"
  ''
  + lib.optionalString (cfg.smtp.enable && cfg.smtp.passwordFile != null) ''
    with open("${cfg.smtp.passwordFile}") as f:
      EMAIL_HOST_PASSWORD = f.read().rstrip("\n")
  ''
  + cfg.extraConfig;
  settings_py =
    pkgs.runCommand "weblate_settings.py"
      {
        inherit weblateConfig;
        passAsFile = [ "weblateConfig" ];
      }
      ''
        mkdir -p $out
        cat \
          ${finalPackage}/${python.sitePackages}/weblate/settings_example.py \
          $weblateConfigPath \
          > $out/settings.py
      '';

  environment = {
    PYTHONPATH = "${settingsDir}:${pythonEnv}/${python.sitePackages}/";
    DJANGO_SETTINGS_MODULE = "settings";
    # We run Weblate through gunicorn, so we can't utilise the env var set in the wrapper.
    inherit (finalPackage) GI_TYPELIB_PATH;
  };

  # Packages needed at runtime
  weblatePath = with pkgs; [
    gitSVN
    subversion
    gettext
    fontconfig
    borgbackup

    #optional
    git-review
    tesseract
    licensee
    mercurial
    openssh
  ];

in
{

  options = {
    services.weblate = {
      enable = lib.mkEnableOption "Weblate service";

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

      localDomain = lib.mkOption {
        description = "The domain name serving your Weblate instance.";
        example = "weblate.example.org";
        type = lib.types.str;
      };

      djangoSecretKeyFile = lib.mkOption {
        description = ''
          Location of the Django secret key.

          This should be a path pointing to a file with secure permissions (not /nix/store).

          Can be generated with `weblate-generate-secret-key` which is available as the `weblate` user.
        '';
        type = lib.types.path;
      };

      configurePostgresql = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = ''
          Whether to enable and configure a local PostgreSQL server by creating a user and database for weblate.
          The default `settings` reference this database, if you disable this option you must provide a database URL in `extraConfig`.
        '';
      };

      extraConfig = lib.mkOption {
        type = lib.types.lines;
        default = "";
        description = ''
          Text to append to `settings.py` Weblate configuration file.
        '';
      };

      smtp = {
        enable = lib.mkEnableOption "Weblate SMTP support";

        from = lib.mkOption {
          description = "The from address being used in sent emails.";
          example = "weblate@example.com";
          default = config.services.weblate.smtp.user;
          defaultText = "config.services.weblate.smtp.user";
          type = lib.types.str;
        };

        user = lib.mkOption {
          description = "SMTP login name.";
          example = "weblate@example.org";
          type = lib.types.nullOr lib.types.str;
          default = null;
        };

        host = lib.mkOption {
          description = "SMTP host used when sending emails to users.";
          type = lib.types.str;
          example = "127.0.0.1";
        };

        port = lib.mkOption {
          description = "SMTP port used when sending emails to users.";
          type = lib.types.port;
          default = 587;
          example = 25;
        };

        passwordFile = lib.mkOption {
          description = ''
            Location of a file containing the SMTP password.

            This should be a path pointing to a file with secure permissions (not /nix/store).
          '';
          type = lib.types.nullOr lib.types.path;
          default = null;
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {

    systemd.tmpfiles.rules = [ "L+ ${settingsDir} - - - - ${settings_py}" ];

    services.nginx = {
      enable = true;
      virtualHosts."${cfg.localDomain}" = {

        forceSSL = true;
        enableACME = true;

        locations = {
          "= /favicon.ico".alias = "${finalPackage}/${python.sitePackages}/weblate/static/favicon.ico";
          "/static/".alias = "${finalPackage.static}/";
          "/".proxyPass = "http://unix:///run/weblate.socket";
        };
      };
    };

    systemd.services.weblate-postgresql-setup = {
      description = "Weblate PostgreSQL setup";
      after = [ "postgresql.target" ];
      serviceConfig = {
        Type = "oneshot";
        User = "postgres";
        Group = "postgres";
        ExecStart = ''
          ${config.services.postgresql.package}/bin/psql weblate -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
        '';
      };
    };

    systemd.services.weblate-migrate = {
      description = "Weblate migration";
      after = [
        "weblate-postgresql-setup.service"
        "redis-weblate.service"
      ];
      requires = [
        "weblate-postgresql-setup.service"
        "redis-weblate.service"
      ];
      # We want this to be active on boot, not just on socket activation
      wantedBy = [ "multi-user.target" ];
      inherit environment;
      path = weblatePath;
      serviceConfig = {
        Type = "oneshot";
        StateDirectory = "weblate";
        User = "weblate";
        Group = "weblate";
        ExecStart = "${finalPackage}/bin/weblate migrate --noinput";
      };
    };

    systemd.services.weblate-celery = {
      description = "Weblate Celery";
      after = [
        "network.target"
        "redis-weblate.service"
        "postgresql.target"
      ];
      # We want this to be active on boot, not just on socket activation
      wantedBy = [ "multi-user.target" ];
      environment = environment // {
        CELERY_WORKER_RUNNING = "1";
      };
      path = weblatePath;
      # Recommendations from:
      # https://github.com/WeblateOrg/weblate/blob/main/weblate/examples/celery-weblate.service
      serviceConfig =
        let
          # We have to push %n through systemd's replacement, therefore %%n.
          pidFile = "/run/celery/weblate-%%n.pid";
          nodes = "celery notify memory backup translate";
          cmd = verb: ''
            ${pythonEnv}/bin/celery multi ${verb} \
              ${nodes} \
              -A "weblate.utils" \
              --pidfile=${pidFile} \
              --logfile=/var/log/celery/weblate-%%n%%I.log \
              --loglevel=DEBUG \
              --beat:celery \
              --queues:celery=celery \
              --prefetch-multiplier:celery=4 \
              --queues:notify=notify \
              --prefetch-multiplier:notify=10 \
              --queues:memory=memory \
              --prefetch-multiplier:memory=10 \
              --queues:translate=translate \
              --prefetch-multiplier:translate=4 \
              --concurrency:backup=1 \
              --queues:backup=backup \
              --prefetch-multiplier:backup=2
          '';
        in
        {
          Type = "forking";
          User = "weblate";
          Group = "weblate";
          WorkingDirectory = "${finalPackage}/${python.sitePackages}/weblate/";
          RuntimeDirectory = "celery";
          RuntimeDirectoryPreserve = "restart";
          LogsDirectory = "celery";
          ExecStart = cmd "start";
          ExecReload = cmd "restart";
          ExecStop = ''
            ${pythonEnv}/bin/celery multi stopwait \
              ${nodes} \
              --pidfile=${pidFile}
          '';
          Restart = "always";
        };
    };

    systemd.services.weblate = {
      description = "Weblate Gunicorn app";
      after = [
        "network.target"
        "weblate-migrate.service"
        "weblate-celery.service"
      ];
      requires = [
        "weblate-migrate.service"
        "weblate-celery.service"
        "weblate.socket"
      ];
      inherit environment;
      path = weblatePath;
      # Weblate generates SSH wrappers with some preset options that use the
      # absolute paths of the ssh and scp binaries internally.
      # As the wrapper is only regenerated when the generator itself is changed,
      # this absolute nix store path becomes unusable once ssh is updated and
      # the path is garbage collected.
      # As generating the wrappers is a quick operation, simply deleting the
      # wrapper directory before service start ensures they are up to date.
      preStart = ''
        if [ -d "${cacheDir}/ssh" ]; then
          rm -r "${cacheDir}/ssh"
        fi
      '';
      serviceConfig = {
        Type = "notify";
        NotifyAccess = "all";
        ExecStart =
          let
            gunicorn = python.pkgs.gunicorn.overridePythonAttrs (old: {
              # Allows Gunicorn to set a meaningful process name
              dependencies = (old.dependencies or [ ]) ++ old.optional-dependencies.setproctitle;
            });
          in
          ''
            ${lib.getExe gunicorn} \
              --name=weblate \
              --bind='unix:///run/weblate.socket' \
              --preload \
              weblate.wsgi
          '';
        ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -s HUP $MAINPID";
        KillMode = "mixed";
        PrivateTmp = true;
        WorkingDirectory = dataDir;
        StateDirectory = "weblate";
        RuntimeDirectory = "weblate";
        User = "weblate";
        Group = "weblate";
      };
    };

    systemd.sockets.weblate = {
      before = [ "nginx.service" ];
      wantedBy = [ "sockets.target" ];
      socketConfig = {
        ListenStream = "/run/weblate.socket";
        SocketUser = "weblate";
        SocketGroup = "weblate";
        SocketMode = "770";
      };
    };

    services.redis.servers.weblate = {
      enable = true;
      user = "weblate";
      unixSocket = "/run/redis-weblate/redis.sock";
      unixSocketPerm = 770;
    };

    services.postgresql = lib.mkIf cfg.configurePostgresql {
      enable = true;
      ensureUsers = [
        {
          name = "weblate";
          ensureDBOwnership = true;
        }
      ];
      ensureDatabases = [ "weblate" ];
    };

    users.users.weblate = {
      isSystemUser = true;
      group = "weblate";
      packages = [ finalPackage ] ++ weblatePath;
    };

    users.groups.weblate.members = [ config.services.nginx.user ];
  };

  meta.maintainers = with lib.maintainers; [ erictapen ];

}
